Spaces:
Running
Running
File size: 10,825 Bytes
ac7a77a | 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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | """Browser regression checks for the static Space.
Install playwright and Chrome/Chromium, then run:
python3 -m unittest discover -s tests -v
Set CHROME_PATH to override the installed browser executable.
"""
import csv
import io
import json
import os
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import shutil
import threading
import unittest
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[1]
SNAPSHOT = json.loads((ROOT / 'front/data/benchmark.json').read_text())
class QuietHandler(SimpleHTTPRequestHandler):
def log_message(self, *args):
pass
class StaticSpaceTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.server = ThreadingHTTPServer(
('127.0.0.1', 0), partial(QuietHandler, directory=str(ROOT)))
cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
cls.thread.start()
cls.base = 'http://127.0.0.1:' + str(cls.server.server_port)
cls.playwright = sync_playwright().start()
cls.browser = cls.playwright.chromium.launch(
executable_path=os.environ.get('CHROME_PATH') or shutil.which('google-chrome')
or shutil.which('chromium'), headless=True, timeout=20000)
@classmethod
def tearDownClass(cls):
cls.browser.close()
cls.playwright.stop()
cls.server.shutdown()
cls.server.server_close()
cls.thread.join()
def setUp(self):
self.context = self.browser.new_context(viewport={'width': 1440, 'height': 1000})
self.page = self.context.new_page()
self.errors = []
self.requests = []
self.page.on('pageerror', lambda error: self.errors.append(str(error)))
self.page.on('request', lambda request: self.requests.append((request.method, request.url)))
def tearDown(self):
self.context.close()
self.assertEqual(self.errors, [])
def open(self, state='ready'):
self.page.goto(self.base, wait_until='domcontentloaded')
self.page.wait_for_selector('#dataStatus[data-state="' + state + '"]')
def refresh(self, state='ready', mobile=False):
with self.page.expect_response('**/front/data/benchmark.json'):
self.page.locator('#refreshMobileBtn' if mobile else '#refreshBtn').click()
self.page.wait_for_selector('#dataStatus[data-state="' + state + '"]')
def test_published_scores_filters_chart_and_export(self):
self.open()
self.assertEqual(self.page.locator('#kpiModels').inner_text(), str(len(SNAPSHOT['models'])))
self.assertEqual(self.page.locator('#datasetFilters input').count(), len(SNAPSHOT['datasets']))
self.assertEqual(self.page.locator('[data-tab="submit"], #submitForm').count(), 0)
self.assertFalse(any('/api/' in url or method != 'GET' for method, url in self.requests))
# Check rendered means against the preserved scoring rules, independently in Python.
excluded = {'멀티 모달', '팩트 체크', 'K-Prism (Text, Intact)',
'K-Prism (Text, Perturbed)', 'K-Prism (Image, Intact)', 'K-Prism (Image, Perturbed)'}
rows = self.page.locator('#tableBody tr').all()
for model in SNAPSHOT['models']:
cells = next(row.locator('td').all() for row in rows
if row.locator('td').nth(2).locator('.font-semibold').inner_text() == model['name'])
scores = {score['dataset_name']: score['score'] for score in model['scores']}
datasets = [d for d in SNAPSHOT['datasets'] if model['is_multimodal'] or d not in excluded]
expected = sum(scores[d] for d in datasets) / len(datasets)
self.assertAlmostEqual(float(cells[3].inner_text()), expected, delta=0.006)
self.page.locator('button[data-fav]').first.click()
self.page.wait_for_function("window.Chart && Chart.getChart('scoresChart').data.datasets.length === 2")
self.page.locator('#searchInput').fill('qwen3-32b')
self.assertEqual(self.page.locator('#kpiModels').inner_text(), '1')
with self.page.expect_download() as download:
self.page.locator('#exportCsvBtn').click()
rows = list(csv.reader(io.StringIO(Path(download.value.path()).read_text())))
self.assertEqual(len(rows), 2)
self.assertEqual(rows[1][2], 'qwen3-32b')
self.page.locator('#searchInput').fill('')
self.page.locator('#providerSelect').select_option('OpenAI')
self.assertEqual(self.page.locator('#kpiModels').inner_text(), '1')
self.page.locator('#searchInput').fill('no-model-matches-this')
self.assertIn('No models match', self.page.locator('#tableBody').inner_text())
def test_preferences_and_repeated_refresh(self):
self.open()
self.page.locator('#clearAllBtn').click()
self.refresh()
self.refresh()
self.assertEqual(self.page.locator('#datasetFilters input:checked').count(), 0)
self.page.reload(wait_until='domcontentloaded')
self.page.wait_for_selector('#dataStatus[data-state="ready"]')
self.assertEqual(self.page.locator('#datasetFilters input:checked').count(), 0)
self.page.locator('#selectAllBtn').click()
self.page.locator('#searchInput').fill('qwen3-32b')
self.page.reload(wait_until='domcontentloaded')
self.page.wait_for_selector('#dataStatus[data-state="ready"]')
self.assertEqual(self.page.locator('#searchInput').input_value(), 'qwen3-32b')
self.assertEqual(self.page.locator('#kpiModels').inner_text(), '1')
def test_initial_failure_never_invents_results_and_retry_recovers(self):
self.page.route('**/front/data/benchmark.json', lambda route: route.fulfill(status=503, body='Unavailable'))
self.open('error')
self.assertEqual(self.page.locator('#kpiModels').inner_text(), '—')
self.assertEqual(self.page.locator('button[data-fav]').count(), 0)
self.assertTrue(self.page.locator('#exportCsvBtn').is_disabled())
self.page.unroute('**/front/data/benchmark.json')
self.refresh()
self.assertEqual(self.page.locator('#kpiModels').inner_text(), str(len(SNAPSHOT['models'])))
def test_malformed_refresh_preserves_previous_results(self):
self.open()
before = self.page.locator('#tableBody').inner_text()
self.page.route('**/front/data/benchmark.json', lambda route: route.fulfill(
content_type='application/json', body='{"models":[{"provider":"Broken"}]}'))
self.refresh('error')
self.assertEqual(self.page.locator('#tableBody').inner_text(), before)
self.assertIn('Previously loaded', self.page.locator('#dataStatus').inner_text())
def test_mobile_controls(self):
self.page.set_viewport_size({'width': 390, 'height': 844})
self.open()
self.assertTrue(self.page.locator('#refreshMobileBtn').is_visible())
self.assertLessEqual(self.page.evaluate('document.documentElement.scrollWidth'), 390)
mean = self.page.locator('#tableBody tr').first.locator('td.mean-col').bounding_box()
self.assertLessEqual(mean['x'] + mean['width'], 390)
before = self.page.locator('html').get_attribute('class') or ''
self.page.locator('#themeToggleMobile').click()
self.assertNotEqual(self.page.locator('html').get_attribute('class') or '', before)
self.refresh(mobile=True)
self.assertEqual(self.page.locator('#kpiModels').inner_text(), str(len(SNAPSHOT['models'])))
def test_blocked_storage_and_unavailable_chart_keep_table_usable(self):
self.page.add_init_script("Object.defineProperty(window, 'localStorage', {get() { throw new Error('blocked'); }});")
self.page.route('https://cdn.jsdelivr.net/npm/chart.js', lambda route: route.abort())
self.open()
self.assertTrue(self.page.locator('#chartUnavailable').is_visible())
self.page.locator('#searchInput').fill('qwen3-32b')
self.assertEqual(self.page.locator('#kpiModels').inner_text(), '1')
self.page.locator('#themeToggle').click()
def test_empty_results_and_untrusted_labels(self):
self.page.route('**/front/data/benchmark.json', lambda route: route.fulfill(
content_type='application/json', body='{"models":[],"datasets":[]}'))
self.open()
self.assertEqual(self.page.locator('#kpiModels').inner_text(), '0')
self.assertIn('No results have been published', self.page.locator('#tableBody').inner_text())
self.page.unroute('**/front/data/benchmark.json')
label = '<img src=x onerror="window.injected=true">'
payload = {'datasets': [label], 'models': [{
'provider': label, 'name': label, 'repo': label, 'is_multimodal': True,
'scores': [{'dataset_name': label, 'score': 42, 'metric_type': 'raw'}]}]}
self.page.route('**/front/data/benchmark.json', lambda route: route.fulfill(
content_type='application/json', body=json.dumps(payload)))
self.refresh()
self.page.locator('#selectAllBtn').click()
self.assertEqual(self.page.locator('#tableBody img, #datasetFilters img').count(), 0)
self.assertFalse(self.page.evaluate('Boolean(window.injected)'))
self.assertIn(label, self.page.locator('#tableBody').inner_text())
def test_legacy_array_validation_and_request_timeout(self):
self.open()
result = self.page.evaluate('''async () => {
const {normalizeBenchmark, loadBenchmark} = await import('/front/data.mjs');
const legacy = normalizeBenchmark([{provider: 'P', name: 'M', scores: [
{dataset_name: 'Additional', score: 0}]}]);
let invalid = 0;
for (const payload of [null, {}, {models: [{provider:'P', name:'M', scores:[
{dataset_name:'A', score:'99'}]}]}, {models:[], datasets:'invalid'}]) {
try { normalizeBenchmark(payload); } catch { invalid++; }
}
window.fetch = (_url, options) => new Promise((_resolve, reject) => {
options.signal.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError')));
});
let timedOut = false;
try { await loadBenchmark('/never-responds', 20); } catch (error) { timedOut = error.name === 'AbortError'; }
return {datasets: legacy.datasets, score: legacy.models[0].scores[0].score, invalid, timedOut};
}''')
self.assertIn('Additional', result['datasets'])
self.assertEqual(result['score'], 0)
self.assertEqual(result['invalid'], 4)
self.assertTrue(result['timedOut'])
if __name__ == '__main__':
unittest.main()
|