25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

98 satır
6.5 KiB

  1. import { chromium, expect } from '@playwright/test';
  2. import assert from 'node:assert/strict';
  3. import fs from 'node:fs';
  4. import { login, call } from './role-permissions-live.mjs';
  5. import manifest from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-manifest.js';
  6. import videos from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-video-manifest.js';
  7. const staticMode = process.env.NARRATION_STATIC === '1';
  8. const dir = process.env.REPORT_DIR || 'reports/bridge-digital-video-20260914';
  9. fs.mkdirSync(dir, { recursive: true });
  10. const origin = process.env.BASE_URL || 'http://127.0.0.1:6180';
  11. const root = await login('root');
  12. const response = await call('tran/v1/teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100', root.accessToken);
  13. assert.equal(response.status, 200);
  14. const codes = process.argv.slice(2);
  15. const packs = manifest.scripts.filter(p => !codes.length || codes.includes(p.code));
  16. const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--enable-unsafe-swiftshader'] });
  17. const results = [], errors = [], requests = [];
  18. let page;
  19. try {
  20. page = await browser.newPage({ viewport: { width: 1600, height: 1000 } });
  21. await page.addInitScript(token => {
  22. sessionStorage.setItem('unreal-tran:web:access-token:v1', token);
  23. localStorage.setItem('unreal-tran:web:preferences:v1', JSON.stringify({ themeMode: 'military' }));
  24. }, root.accessToken);
  25. page.on('pageerror', e => errors.push(e.message));
  26. page.on('request', request => requests.push(new URL(request.url()).pathname));
  27. const ready = async () => {
  28. await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/, { timeout: 120000 });
  29. await expect(page.locator('[data-role="step-count"]')).toBeVisible();
  30. };
  31. const media = page.locator('.training-server-video');
  32. const play = async () => {
  33. if (await page.locator('[data-role="teacher-launch"]').isVisible()) await page.locator('[data-role="teacher-launch"]').click();
  34. await page.locator('[data-action="teacher-speak"]').click();
  35. await expect(media).toBeVisible();
  36. await expect.poll(() => media.evaluate(v => v.currentTime), { timeout: 15000 }).toBeGreaterThan(.1);
  37. assert.equal(await media.evaluate(v => v.error), null);
  38. assert.ok(await media.evaluate(v => v.videoWidth === 480 && v.videoHeight === 640));
  39. };
  40. for (const pack of packs) {
  41. const task = response.data.records.find(t => t.code === pack.code); assert.ok(task);
  42. const videoPack = staticMode ? videos.scripts.find(item => item.id === pack.id && item.version === pack.version) : null;
  43. const obsoleteDownloads = [];
  44. if (staticMode) {
  45. assert.equal(videoPack.steps.length, pack.steps.length);
  46. const assets = await call(`tran/v1/teaching/assignments/${task.id}/assets`, root.accessToken);
  47. assert.equal(assets.status, 200);
  48. const replaced = new Set(task.definitionSnapshot.steps.map(step => step.audio?.narrationVideoAssetCode?.toUpperCase()).filter(Boolean));
  49. for (const asset of assets.data) if (asset.sourceVersionId === task.contentVersionId && replaced.has(asset.assetCode.toUpperCase())) {
  50. obsoleteDownloads.push(new URL(asset.downloadUrl, origin).pathname);
  51. }
  52. requests.length = 0;
  53. }
  54. await page.goto(`${origin}/teaching/virtual-training/preview/${task.id}`);
  55. await ready(); await play();
  56. if (staticMode) assert.equal(await media.evaluate(v => new URL(v.currentSrc).pathname), videoPack.steps[0].url);
  57. assert.equal(await page.locator('[data-role="teacher-image"]').evaluate(e => getComputedStyle(e).visibility), 'hidden');
  58. await page.screenshot({ path: `${dir}/${task.code}-video.png` });
  59. const button = page.locator('[data-action="toggle-teacher-media"]');
  60. await button.click();
  61. await expect(media).toBeHidden();
  62. await expect.poll(() => media.evaluate(v => new URL(v.currentSrc || v.src, location.href).pathname)).toBe(pack.steps[0].url);
  63. await expect.poll(() => media.evaluate(v => v.currentTime)).toBeGreaterThan(.1);
  64. await button.click(); await expect(media).toBeVisible();
  65. const voice = page.locator('[data-action="toggle-teacher-voice"]');
  66. if (await voice.getAttribute('aria-pressed') === 'true') await voice.click();
  67. for (let op = 0; op < task.definitionSnapshot.steps[0].action.mechanism.steps.length; op++) {
  68. await page.locator('[data-role="part-label"]').click();
  69. if (pack.code !== 'TASK-VIRTUAL-005') await expect.poll(() => page.evaluate(() => globalThis.__trainingSceneDiagnostics().bridgeWorkshop.pending)).toBe(false);
  70. else await page.waitForTimeout(1800);
  71. }
  72. await expect(page.locator('[data-action="next"]')).toBeEnabled({ timeout: 20000 });
  73. await play(); const first = await media.getAttribute('src');
  74. await page.locator('[data-action="next"]').click();
  75. await expect(page.locator('[data-role="step-count"]')).toHaveText(`2 / ${pack.steps.length}`);
  76. assert.equal(await media.evaluate(v => v.paused), true);
  77. await play(); assert.notEqual(await media.getAttribute('src'), first);
  78. if (staticMode) assert.equal(await media.evaluate(v => new URL(v.currentSrc).pathname), videoPack.steps[1].url);
  79. await media.evaluate(v => { v.src = 'data:video/mp4;base64,AA=='; v.load(); });
  80. await expect(media).toBeHidden();
  81. await expect.poll(() => media.evaluate(v => new URL(v.currentSrc || v.src, location.href).pathname)).toBe(pack.steps[1].url);
  82. await expect.poll(() => media.evaluate(v => v.currentTime)).toBeGreaterThan(.1);
  83. await page.reload(); await ready(); await play();
  84. await page.locator('.training-teacher-bubble [data-action="toggle-teacher"]').click();
  85. assert.equal(await media.evaluate(v => v.paused), true);
  86. if (staticMode) assert.deepEqual(requests.filter(url => obsoleteDownloads.includes(url)), [], 'Static narration must not download obsolete video assets');
  87. results.push({ code: pack.code, firstAndSecondVideo: true, audioToggle: true, stopOnStepChange: true, errorFallback: true, reloadAndClose: true,
  88. ...(staticMode ? { staticVideoUrls: videoPack.steps.slice(0, 2).map(clip => clip.url), obsoleteAssetDownloads: 0 } : {}) });
  89. console.log('PASS', pack.code);
  90. }
  91. assert.deepEqual(errors, []);
  92. fs.writeFileSync(`${dir}/${origin.includes('127.0.0.1') ? 'live' : 'lan'}-result.json`, JSON.stringify({ origin, results, errors }, null, 2));
  93. } catch (error) {
  94. if (page) await page.screenshot({ path: `${dir}/failure.png` }).catch(() => {});
  95. fs.writeFileSync(`${dir}/failure.json`, JSON.stringify({ message: error.message, results, errors }, null, 2));
  96. throw error;
  97. } finally { await browser.close(); }