|
- import { chromium, expect } from '@playwright/test';
- import assert from 'node:assert/strict';
- import fs from 'node:fs';
- import { login, call } from './role-permissions-live.mjs';
- import manifest from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-manifest.js';
- import videos from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-video-manifest.js';
- const staticMode = process.env.NARRATION_STATIC === '1';
- const dir = process.env.REPORT_DIR || 'reports/bridge-digital-video-20260914';
- fs.mkdirSync(dir, { recursive: true });
- const origin = process.env.BASE_URL || 'http://127.0.0.1:6180';
- const root = await login('root');
- const response = await call('tran/v1/teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100', root.accessToken);
- assert.equal(response.status, 200);
- const codes = process.argv.slice(2);
- const packs = manifest.scripts.filter(p => !codes.length || codes.includes(p.code));
- const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--enable-unsafe-swiftshader'] });
- const results = [], errors = [], requests = [];
- let page;
- try {
- page = await browser.newPage({ viewport: { width: 1600, height: 1000 } });
- await page.addInitScript(token => {
- sessionStorage.setItem('unreal-tran:web:access-token:v1', token);
- localStorage.setItem('unreal-tran:web:preferences:v1', JSON.stringify({ themeMode: 'military' }));
- }, root.accessToken);
- page.on('pageerror', e => errors.push(e.message));
- page.on('request', request => requests.push(new URL(request.url()).pathname));
- const ready = async () => {
- await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/, { timeout: 120000 });
- await expect(page.locator('[data-role="step-count"]')).toBeVisible();
- };
- const media = page.locator('.training-server-video');
- const play = async () => {
- if (await page.locator('[data-role="teacher-launch"]').isVisible()) await page.locator('[data-role="teacher-launch"]').click();
- await page.locator('[data-action="teacher-speak"]').click();
- await expect(media).toBeVisible();
- await expect.poll(() => media.evaluate(v => v.currentTime), { timeout: 15000 }).toBeGreaterThan(.1);
- assert.equal(await media.evaluate(v => v.error), null);
- assert.ok(await media.evaluate(v => v.videoWidth === 480 && v.videoHeight === 640));
- };
- for (const pack of packs) {
- const task = response.data.records.find(t => t.code === pack.code); assert.ok(task);
- const videoPack = staticMode ? videos.scripts.find(item => item.id === pack.id && item.version === pack.version) : null;
- const obsoleteDownloads = [];
- if (staticMode) {
- assert.equal(videoPack.steps.length, pack.steps.length);
- const assets = await call(`tran/v1/teaching/assignments/${task.id}/assets`, root.accessToken);
- assert.equal(assets.status, 200);
- const replaced = new Set(task.definitionSnapshot.steps.map(step => step.audio?.narrationVideoAssetCode?.toUpperCase()).filter(Boolean));
- for (const asset of assets.data) if (asset.sourceVersionId === task.contentVersionId && replaced.has(asset.assetCode.toUpperCase())) {
- obsoleteDownloads.push(new URL(asset.downloadUrl, origin).pathname);
- }
- requests.length = 0;
- }
- await page.goto(`${origin}/teaching/virtual-training/preview/${task.id}`);
- await ready(); await play();
- if (staticMode) assert.equal(await media.evaluate(v => new URL(v.currentSrc).pathname), videoPack.steps[0].url);
- assert.equal(await page.locator('[data-role="teacher-image"]').evaluate(e => getComputedStyle(e).visibility), 'hidden');
- await page.screenshot({ path: `${dir}/${task.code}-video.png` });
- const button = page.locator('[data-action="toggle-teacher-media"]');
- await button.click();
- await expect(media).toBeHidden();
- await expect.poll(() => media.evaluate(v => new URL(v.currentSrc || v.src, location.href).pathname)).toBe(pack.steps[0].url);
- await expect.poll(() => media.evaluate(v => v.currentTime)).toBeGreaterThan(.1);
- await button.click(); await expect(media).toBeVisible();
- const voice = page.locator('[data-action="toggle-teacher-voice"]');
- if (await voice.getAttribute('aria-pressed') === 'true') await voice.click();
- for (let op = 0; op < task.definitionSnapshot.steps[0].action.mechanism.steps.length; op++) {
- await page.locator('[data-role="part-label"]').click();
- if (pack.code !== 'TASK-VIRTUAL-005') await expect.poll(() => page.evaluate(() => globalThis.__trainingSceneDiagnostics().bridgeWorkshop.pending)).toBe(false);
- else await page.waitForTimeout(1800);
- }
- await expect(page.locator('[data-action="next"]')).toBeEnabled({ timeout: 20000 });
- await play(); const first = await media.getAttribute('src');
- await page.locator('[data-action="next"]').click();
- await expect(page.locator('[data-role="step-count"]')).toHaveText(`2 / ${pack.steps.length}`);
- assert.equal(await media.evaluate(v => v.paused), true);
- await play(); assert.notEqual(await media.getAttribute('src'), first);
- if (staticMode) assert.equal(await media.evaluate(v => new URL(v.currentSrc).pathname), videoPack.steps[1].url);
- await media.evaluate(v => { v.src = 'data:video/mp4;base64,AA=='; v.load(); });
- await expect(media).toBeHidden();
- await expect.poll(() => media.evaluate(v => new URL(v.currentSrc || v.src, location.href).pathname)).toBe(pack.steps[1].url);
- await expect.poll(() => media.evaluate(v => v.currentTime)).toBeGreaterThan(.1);
- await page.reload(); await ready(); await play();
- await page.locator('.training-teacher-bubble [data-action="toggle-teacher"]').click();
- assert.equal(await media.evaluate(v => v.paused), true);
- if (staticMode) assert.deepEqual(requests.filter(url => obsoleteDownloads.includes(url)), [], 'Static narration must not download obsolete video assets');
- results.push({ code: pack.code, firstAndSecondVideo: true, audioToggle: true, stopOnStepChange: true, errorFallback: true, reloadAndClose: true,
- ...(staticMode ? { staticVideoUrls: videoPack.steps.slice(0, 2).map(clip => clip.url), obsoleteAssetDownloads: 0 } : {}) });
- console.log('PASS', pack.code);
- }
- assert.deepEqual(errors, []);
- fs.writeFileSync(`${dir}/${origin.includes('127.0.0.1') ? 'live' : 'lan'}-result.json`, JSON.stringify({ origin, results, errors }, null, 2));
- } catch (error) {
- if (page) await page.screenshot({ path: `${dir}/failure.png` }).catch(() => {});
- fs.writeFileSync(`${dir}/failure.json`, JSON.stringify({ message: error.message, results, errors }, null, 2));
- throw error;
- } finally { await browser.close(); }
|