Mercurial
comparison mrjunejune/test/theme_and_webp_test.js @ 260:1f9877b637e9
Add Copilot-powered cyberpunk JRPG chat
Integrate the production JRPG chat with Seobeo streaming, Deita persistence, and a Bazel-managed Copilot SDK and LiteLLM inference stack.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <mrjunejune@users.noreply.github.com> |
|---|---|
| date | Wed, 05 Aug 2026 09:19:41 -0700 |
| parents | 667156fcd3e3 |
| children | b401627fc49e |
comparison
equal
deleted
inserted
replaced
| 259:667156fcd3e3 | 260:1f9877b637e9 |
|---|---|
| 1 const assert = require('node:assert/strict'); | 1 const assert = require('node:assert/strict'); |
| 2 const fs = require('node:fs'); | 2 const fs = require('node:fs'); |
| 3 const net = require('node:net'); | 3 const net = require('node:net'); |
| 4 const os = require('node:os'); | |
| 4 const path = require('node:path'); | 5 const path = require('node:path'); |
| 5 const { spawn } = require('node:child_process'); | 6 const { spawn } = require('node:child_process'); |
| 6 | 7 |
| 7 const RUNFILES = process.env.JS_BINARY__RUNFILES; | 8 const RUNFILES = process.env.JS_BINARY__RUNFILES; |
| 8 const WORKSPACE = process.env.JS_BINARY__WORKSPACE; | 9 const WORKSPACE = process.env.JS_BINARY__WORKSPACE; |
| 127 await context.addInitScript(value => { | 128 await context.addInitScript(value => { |
| 128 localStorage.setItem('theme-preference', value); | 129 localStorage.setItem('theme-preference', value); |
| 129 }, theme); | 130 }, theme); |
| 130 const page = await context.newPage(); | 131 const page = await context.newPage(); |
| 131 const errors = []; | 132 const errors = []; |
| 133 await page.route('**/api/conversations**', async route => { | |
| 134 const request = route.request(); | |
| 135 const url = new URL(request.url()); | |
| 136 if (request.method() === 'POST' && | |
| 137 url.pathname === '/api/conversations') { | |
| 138 await route.fulfill({ | |
| 139 status: 201, | |
| 140 contentType: 'application/json', | |
| 141 body: JSON.stringify({ | |
| 142 id: '11111111-1111-4111-8111-111111111111', | |
| 143 }), | |
| 144 }); | |
| 145 return; | |
| 146 } | |
| 147 if (request.method() === 'POST' && url.pathname.endsWith('/turns')) { | |
| 148 await route.fulfill({ | |
| 149 status: 200, | |
| 150 contentType: 'text/event-stream', | |
| 151 body: [ | |
| 152 'event: turn.accepted', | |
| 153 'data: {"request_id":"request-1"}', | |
| 154 '', | |
| 155 'event: assistant.delta', | |
| 156 'data: {"request_id":"request-1","delta":"Start with "}', | |
| 157 '', | |
| 158 'event: assistant.delta', | |
| 159 'data: {"request_id":"request-1","delta":"the tools."}', | |
| 160 '', | |
| 161 'event: assistant.completed', | |
| 162 'data: {"request_id":"request-1","content":"Start with the tools."}', | |
| 163 '', | |
| 164 'event: turn.done', | |
| 165 'data: {"request_id":"request-1","failed":false,"aborted":false}', | |
| 166 '', | |
| 167 '', | |
| 168 ].join('\n'), | |
| 169 }); | |
| 170 return; | |
| 171 } | |
| 172 await route.fulfill({ | |
| 173 status: 404, | |
| 174 contentType: 'application/json', | |
| 175 body: JSON.stringify({ error: { message: 'Not found' } }), | |
| 176 }); | |
| 177 }); | |
| 132 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); | 178 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); |
| 133 page.on('console', message => { | 179 page.on('console', message => { |
| 134 if (message.type() === 'error') errors.push(`console: ${message.text()}`); | 180 if (message.type() === 'error' && |
| 181 !message.text().startsWith('Failed to load resource:')) { | |
| 182 errors.push(`console: ${message.text()}`); | |
| 183 } | |
| 135 }); | 184 }); |
| 136 page.on('requestfailed', request => { | 185 page.on('requestfailed', request => { |
| 137 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); | 186 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); |
| 138 }); | 187 }); |
| 139 page.on('response', response => { | 188 page.on('response', response => { |
| 189 if (response.status() === 404 && | |
| 190 response.url().endsWith( | |
| 191 '/api/conversations/00000000-0000-4000-8000-000000000000' | |
| 192 )) { | |
| 193 return; | |
| 194 } | |
| 140 if (response.status() >= 400) { | 195 if (response.status() >= 400) { |
| 141 errors.push(`response: ${response.status()} ${response.url()}`); | 196 errors.push(`response: ${response.status()} ${response.url()}`); |
| 142 } | 197 } |
| 143 }); | 198 }); |
| 144 | 199 |
| 376 async function testJrpgPage(browser) { | 431 async function testJrpgPage(browser) { |
| 377 const context = await browser.newContext({ | 432 const context = await browser.newContext({ |
| 378 colorScheme: 'light', | 433 colorScheme: 'light', |
| 379 viewport: { width: 1280, height: 900 }, | 434 viewport: { width: 1280, height: 900 }, |
| 380 }); | 435 }); |
| 436 await context.addInitScript(key => { | |
| 437 sessionStorage.setItem( | |
| 438 key, | |
| 439 '00000000-0000-4000-8000-000000000000', | |
| 440 ); | |
| 441 }, 'mjj-jrpg-conversation-id'); | |
| 381 const page = await context.newPage(); | 442 const page = await context.newPage(); |
| 382 const errors = []; | 443 const errors = []; |
| 383 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); | 444 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); |
| 384 page.on('console', message => { | 445 page.on('console', message => { |
| 385 if (message.type() === 'error') errors.push(`console: ${message.text()}`); | 446 if (message.type() === 'error' && |
| 447 !message.text().startsWith('Failed to load resource:')) { | |
| 448 errors.push(`console: ${message.text()}`); | |
| 449 } | |
| 386 }); | 450 }); |
| 387 page.on('requestfailed', request => { | 451 page.on('requestfailed', request => { |
| 388 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); | 452 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); |
| 389 }); | 453 }); |
| 390 page.on('response', response => { | 454 page.on('response', response => { |
| 455 if (response.status() === 404 && | |
| 456 response.url().endsWith( | |
| 457 '/api/conversations/00000000-0000-4000-8000-000000000000' | |
| 458 )) { | |
| 459 return; | |
| 460 } | |
| 391 if (response.status() >= 400) { | 461 if (response.status() >= 400) { |
| 392 errors.push(`response: ${response.status()} ${response.url()}`); | 462 errors.push(`response: ${response.status()} ${response.url()}`); |
| 393 } | 463 } |
| 394 }); | 464 }); |
| 395 | 465 |
| 433 const computedFonts = [ | 503 const computedFonts = [ |
| 434 document.body, | 504 document.body, |
| 435 document.querySelector('.jrpg-brand h1'), | 505 document.querySelector('.jrpg-brand h1'), |
| 436 document.querySelector('.jrpg-message p'), | 506 document.querySelector('.jrpg-message p'), |
| 437 document.querySelector('#jrpg-message'), | 507 document.querySelector('#jrpg-message'), |
| 438 document.querySelector('.jrpg-composer button'), | 508 document.querySelector('.jrpg-composer button[type="submit"]'), |
| 439 ].map(element => getComputedStyle(element).fontFamily); | 509 ].map(element => getComputedStyle(element).fontFamily); |
| 440 const nonPixelText = [document.body, ...document.body.querySelectorAll('*')] | 510 const nonPixelText = [document.body, ...document.body.querySelectorAll('*')] |
| 441 .filter(element => [...element.childNodes].some(node => | 511 .filter(element => [...element.childNodes].some(node => |
| 442 node.nodeType === Node.TEXT_NODE && node.textContent.trim() | 512 node.nodeType === Node.TEXT_NODE && node.textContent.trim() |
| 443 )) | 513 )) |
| 445 getComputedStyle(element).fontFamily !== '"Pixel Mplus"' | 515 getComputedStyle(element).fontFamily !== '"Pixel Mplus"' |
| 446 ) | 516 ) |
| 447 .map(element => element.localName); | 517 .map(element => element.localName); |
| 448 const pageStyle = getComputedStyle(document.body); | 518 const pageStyle = getComputedStyle(document.body); |
| 449 const submitStyle = getComputedStyle( | 519 const submitStyle = getComputedStyle( |
| 450 document.querySelector('.jrpg-composer button'), | 520 document.querySelector('.jrpg-composer button[type="submit"]'), |
| 451 ); | 521 ); |
| 452 const colors = { | 522 const colors = { |
| 453 canvas: probeStyle.backgroundColor, | 523 canvas: probeStyle.backgroundColor, |
| 454 conversation: getComputedStyle( | 524 conversation: getComputedStyle( |
| 455 document.querySelector('mjj-jrpg-chat'), | 525 document.querySelector('mjj-jrpg-chat'), |
| 535 await page.locator('.jrpg-brand a').evaluate(element => | 605 await page.locator('.jrpg-brand a').evaluate(element => |
| 536 getComputedStyle(element).outlineColor | 606 getComputedStyle(element).outlineColor |
| 537 ), | 607 ), |
| 538 desktop.colors.focus, | 608 desktop.colors.focus, |
| 539 ); | 609 ); |
| 540 await page.locator('.jrpg-composer button').focus(); | 610 await page.locator('.jrpg-composer button[type="submit"]').focus(); |
| 541 assert.equal( | 611 assert.equal( |
| 542 await page.locator('.jrpg-composer button').evaluate(element => | 612 await page.locator('.jrpg-composer button[type="submit"]').evaluate(element => |
| 543 getComputedStyle(element).outlineColor | 613 getComputedStyle(element).outlineColor |
| 544 ), | 614 ), |
| 545 desktop.colors.focus, | 615 desktop.colors.focus, |
| 546 ); | 616 ); |
| 547 assert.equal( | 617 assert.equal( |
| 894 const serverBinary = path.join( | 964 const serverBinary = path.join( |
| 895 runfilesWorkspace, | 965 runfilesWorkspace, |
| 896 'mrjunejune/mrjunejune_server_debug', | 966 'mrjunejune/mrjunejune_server_debug', |
| 897 ); | 967 ); |
| 898 const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src'); | 968 const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src'); |
| 969 const fakeSidecar = path.join( | |
| 970 runfilesWorkspace, | |
| 971 'mrjunejune/test/inference_bridge_fake_sidecar', | |
| 972 ); | |
| 973 const tempDirectory = fs.mkdtempSync( | |
| 974 path.join(os.tmpdir(), 'mrjunejune-theme-test-'), | |
| 975 ); | |
| 899 const chromiumPath = path.resolve(process.env.CHROMIUM_PATH); | 976 const chromiumPath = path.resolve(process.env.CHROMIUM_PATH); |
| 900 const serverLogs = []; | 977 const serverLogs = []; |
| 901 let server; | 978 let server; |
| 902 let browser; | 979 let browser; |
| 903 | 980 |
| 935 ); | 1012 ); |
| 936 } | 1013 } |
| 937 | 1014 |
| 938 server = spawn(serverBinary, [], { | 1015 server = spawn(serverBinary, [], { |
| 939 cwd: runfilesWorkspace, | 1016 cwd: runfilesWorkspace, |
| 940 env: { ...process.env, MRJUNEJUNE_PORT: port }, | 1017 env: { |
| 1018 ...process.env, | |
| 1019 MRJUNEJUNE_PORT: port, | |
| 1020 MRJUNEJUNE_DB_PATH: path.join(tempDirectory, 'site.db'), | |
| 1021 MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar, | |
| 1022 MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar, | |
| 1023 MRJUNEJUNE_ALLOW_ANONYMOUS_INFERENCE: '1', | |
| 1024 }, | |
| 941 stdio: ['ignore', 'pipe', 'pipe'], | 1025 stdio: ['ignore', 'pipe', 'pipe'], |
| 942 }); | 1026 }); |
| 943 server.stdout.on('data', chunk => serverLogs.push(chunk.toString())); | 1027 server.stdout.on('data', chunk => serverLogs.push(chunk.toString())); |
| 944 server.stderr.on('data', chunk => serverLogs.push(chunk.toString())); | 1028 server.stderr.on('data', chunk => serverLogs.push(chunk.toString())); |
| 945 await waitForServer(server, serverLogs); | 1029 await waitForServer(server, serverLogs); |
| 955 await fetch(`${baseUrl}/sw.js`) | 1039 await fetch(`${baseUrl}/sw.js`) |
| 956 ).text(); | 1040 ).text(); |
| 957 for (const source of [home, dogGame, manifest]) { | 1041 for (const source of [home, dogGame, manifest]) { |
| 958 assert.doesNotMatch(source, /\.png(?:["')]|$)/i); | 1042 assert.doesNotMatch(source, /\.png(?:["')]|$)/i); |
| 959 } | 1043 } |
| 960 assert.match(serviceWorker, /v9-dev-page-split/); | 1044 assert.match(serviceWorker, /v10-inference-chat/); |
| 961 assert.match( | 1045 assert.match( |
| 962 await ( | 1046 await ( |
| 963 await fetch(`${baseUrl}/public/pwa-register.js`) | 1047 await fetch(`${baseUrl}/public/pwa-register.js`) |
| 964 ).text(), | 1048 ).text(), |
| 965 /register\('\/sw\.js', \{ scope: '\/' \}\)/, | 1049 /register\('\/sw\.js', \{ scope: '\/' \}\)/, |
| 1025 await testJrpgPage(browser); | 1109 await testJrpgPage(browser); |
| 1026 await testHlsPlayer(browser, siteRoot); | 1110 await testHlsPlayer(browser, siteRoot); |
| 1027 } finally { | 1111 } finally { |
| 1028 if (browser) await browser.close(); | 1112 if (browser) await browser.close(); |
| 1029 await stopProcess(server); | 1113 await stopProcess(server); |
| 1114 fs.rmSync(tempDirectory, { recursive: true, force: true }); | |
| 1030 } | 1115 } |
| 1031 })().catch(error => { | 1116 })().catch(error => { |
| 1032 console.error(error.stack || error); | 1117 console.error(error.stack || error); |
| 1033 process.exitCode = 1; | 1118 process.exitCode = 1; |
| 1034 }); | 1119 }); |