File size: 7,347 Bytes
cc3f990
 
 
 
 
 
 
e9d0e73
cc3f990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294f8ea
 
 
 
 
 
e60eab9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc3f990
e60eab9
cc3f990
 
 
 
 
 
 
 
 
 
 
 
 
e60eab9
cc3f990
 
 
 
 
 
 
 
e60eab9
 
 
 
cc3f990
 
e60eab9
 
cc3f990
e60eab9
cc3f990
 
e60eab9
cc3f990
e60eab9
cc3f990
 
 
e60eab9
cc3f990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e60eab9
cc3f990
 
e60eab9
cc3f990
e60eab9
cc3f990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* Real-browser smoke test against an already-running playground; no fixtures. */
const assert = require('node:assert/strict');
const fs = require('node:fs/promises');
const path = require('node:path');
const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright-core');

async function main() {
  const url = process.argv[2] || 'http://127.0.0.1:7466';
  const output = process.argv[3];
  if (!output || !process.env.CHROMIUM_PATH) {
    throw new Error('Provide URL, a new output directory and CHROMIUM_PATH.');
  }
  await fs.mkdir(output, { recursive: false });
  const browser = await chromium.launch({
    executablePath: process.env.CHROMIUM_PATH,
    headless: true,
    args: ['--disable-gpu', '--no-sandbox'],
  });
  const report = { started_utc: new Date().toISOString(), url, real_model_responses: [], checks: [] };
  try {
    const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } });
    const page = await context.newPage();
    const errors = [];
    page.on('pageerror', error => errors.push(error.message));
    page.on('console', message => {
      if (message.type() === 'error' && /Content Security Policy|Refused to/.test(message.text())) {
        errors.push(message.text());
      }
    });
    await page.goto(url);
    await page.waitForFunction(() => !document.getElementById('model').disabled);
    const ids = await page.locator('#model option').evaluateAll(options => options.map(option => option.value));
    assert(ids.length >= 2, 'Expected multiple real models');
    const catalog = await (await page.request.get(new URL('/api/models', url).href)).json();
    const selectedId = await page.locator('#model').inputValue();
    const selectedModel = catalog.models.find(model => model.id === selectedId);
    assert(selectedModel, 'Selected model must exist in the live catalog');
    assert.equal(await page.locator('#calibration-badge').innerText(),
      selectedModel.calibrated ? 'Calibrated checkpoint' : 'Uncalibrated checkpoint');
    const revealPane = async (name) => {
      if (await page.locator('#workspace-tabs').isVisible()) {
        await page.locator(`#tab-${name}`).click();
      }
    };
    const assertNoDocumentOverflow = async (layout) => {
      const dimensions = await page.evaluate(() => ({
        width: document.documentElement.scrollWidth,
        height: document.documentElement.scrollHeight,
        viewportWidth: window.innerWidth,
        viewportHeight: window.innerHeight,
      }));
      assert(dimensions.width <= dimensions.viewportWidth, `${layout}: horizontal document overflow`);
      assert(dimensions.height <= dimensions.viewportHeight, `${layout}: vertical document overflow`);
    };
    const score = async (model) => {
      await revealPane('context');
      await page.selectOption('#model', model);
      await page.waitForFunction(() => !document.getElementById('submit').disabled);
      const responsePromise = page.waitForResponse(response =>
        new URL(response.url()).pathname === '/api/score' && response.request().method() === 'POST',
      { timeout: 300000 });
      await page.locator('#submit').click();
      const response = await responsePromise;
      const body = await response.json();
      assert.equal(response.status(), 200, JSON.stringify(body));
      assert.equal(body.model.id, model);
      assert.equal(body.probabilities.length, 3);
      assert(Math.abs(body.probabilities.reduce((sum, row) => sum + row.probability, 0) - 1) < 1e-5);
      assert(body.probabilities.every(row => Number.isFinite(row.probability) && row.probability >= 0));
      await revealPane('results');
      await page.locator('#results-content').waitFor({ state: 'visible' });
      assert.equal(await page.locator('.probability-item').count(), 3);
      assert.equal(await page.locator('#stale-notice').isVisible(), false);
      report.real_model_responses.push(body);
      return body;
    };
    for (const [index, model] of ids.entries()) {
      await score(model);
      if (!index) {
        await assertNoDocumentOverflow('Desktop');
        await page.screenshot({ path: path.join(output, 'desktop.png'), fullPage: true });
      }
    }
    report.checks.push('Real browser scoring and normalized distributions for every configured model');
    report.checks.push('Desktop layout has no horizontal or vertical document overflow');
    await revealPane('choices');
    await page.locator('#question').fill('Which choice fits the context best?');
    await revealPane('results');
    assert.equal(await page.locator('#stale-notice').isVisible(), true);
    report.checks.push('Input edits visibly mark old results as stale');
    await revealPane('choices');
    await page.locator('#options').fill('Duplicate\nDuplicate');
    await revealPane('context');
    await page.locator('#submit').click();
    assert.match(await page.locator('#error-message').innerText(), /duplicate|different/i);
    report.checks.push('Duplicate options rejected without scoring');
    await revealPane('context');
    await page.locator('#reset').click();
    await score(ids[0]);
    await context.grantPermissions(['clipboard-read', 'clipboard-write']);
    await page.locator('#copy-results').click();
    await page.waitForFunction(() => document.getElementById('copy-label').textContent === 'Copied');
    assert.equal(await page.locator('#copy-label').innerText(), 'Copied');
    const copied = JSON.parse(await page.evaluate(() => navigator.clipboard.readText()));
    assert.equal(copied.model.id, ids[0]);
    report.checks.push('Copy JSON returns the actual selected model result');
    const tokenError = await page.evaluate(async () => {
      const response = await fetch('/api/score', { method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ model: document.getElementById('model').value,
          text: 'A very long invented context. '.repeat(1500), question: 'Which option?', options: ['First', 'Second'] }) });
      return { status: response.status, body: await response.json() };
    });
    assert.equal(tokenError.status, 422);
    assert.equal(tokenError.body.code, 'token_limit');
    report.checks.push('Actual tokenizer rejects oversized input without truncation');
    await page.setViewportSize({ width: 390, height: 844 });
    await revealPane('context');
    await page.locator('#reset').click();
    await score(ids[0]);
    await assertNoDocumentOverflow('Mobile');
    await page.screenshot({ path: path.join(output, 'mobile.png'), fullPage: true });
    report.checks.push('Mobile layout has no horizontal or vertical document overflow and real results remain usable');
    assert.deepEqual(errors, []);
    report.checks.push('No browser script or Content Security Policy errors');
    report.status = 'passed';
  } catch (error) {
    report.status = 'failed';
    report.error = error.message;
    throw error;
  } finally {
    report.finished_utc = new Date().toISOString();
    await fs.writeFile(path.join(output, 'browser-verification.json'), JSON.stringify(report, null, 2) + '\n');
    await browser.close();
  }
  console.log(JSON.stringify({ status: report.status, models: report.real_model_responses.map(row => row.model.id), output }));
}

main().catch(error => { console.error(error.message); process.exitCode = 1; });