Files
vision-start/scripts/check_regressions.mjs
T
ivanch b9159cf61f
Build and Release to Staging / Build Vision Start (push) Successful in 1m1s
Build and Release to Staging / Build Vision Start Image (push) Successful in 2m33s
Build and Release to Staging / Deploy Vision Start (staging) (push) Successful in 14s
improving general usability and stability
2026-10-09 19:30:19 -03:00

326 lines
17 KiB
JavaScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { resolve } from 'node:path';
import { mkdtemp, cp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { build, preview } from 'vite';
import { chromium } from 'playwright';
const loadModule = async (entry) => {
const result = await build({
configFile: false,
logLevel: 'silent',
build: {
write: false,
minify: false,
lib: { entry: resolve(entry), formats: ['es'] },
},
});
const output = Array.isArray(result) ? result[0] : result;
const code = output.output.find(chunk => chunk.type === 'chunk' && chunk.isEntry).code;
return import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`);
};
const storage = new Map();
const chromeStorage = new Map();
let failWrite = null;
let maximumStorageBytes = Infinity;
let chromeReads = 0;
globalThis.localStorage = {
getItem: key => storage.get(key) ?? null,
setItem: (key, value) => {
if (key === failWrite) {
failWrite = null;
throw new Error('Quota exceeded');
}
const next = new Map(storage).set(key, value);
if ([...next.values()].reduce((bytes, entry) => bytes + entry.length, 0) > maximumStorageBytes) throw new Error('Quota exceeded');
storage.set(key, value);
},
removeItem: key => storage.delete(key),
};
globalThis.window = { chrome: { storage: { local: {
get: (keys, callback) => {
chromeReads++;
callback(Object.fromEntries(keys.map(key => [key, chromeStorage.get(key)])));
},
set: (values, callback) => {
Object.entries(values).forEach(([key, value]) => chromeStorage.set(key, value));
callback();
},
remove: (key, callback) => { chromeStorage.delete(key); callback(); },
} } } };
globalThis.FileReader = class {
readAsDataURL(blob) {
blob.arrayBuffer().then(buffer => {
this.result = `data:${blob.type};base64,${Buffer.from(buffer).toString('base64')}`;
this.onloadend?.();
this.onload?.();
});
}
};
const [{ ConfigurationService, DEFAULT_CONFIG, normalizeConfig, normalizeCategories }, icons, { default: ping }] = await Promise.all([
loadModule('components/services/ConfigurationService.ts'),
loadModule('components/utils/iconService.ts'),
loadModule('components/utils/jsping.ts'),
]);
const file = value => new File([JSON.stringify(value)], 'config.json', { type: 'application/json' });
const iconUrl = name => `https://www.google.com/s2/favicons?domain=${name}.com&sz=128`;
const originalFetch = globalThis.fetch;
await test('malformed stored data recovers without shared defaults or unsafe polling rates', () => {
const config = normalizeConfig({ currentWallpapers: [null, 'Beach', 'Beach'], serverWidget: { pingFrequency: -1, servers: [null, {}, { id: '1', name: 'Server', address: 'localhost' }] }, clock: { size: 'unknown' }, wallpaperBlur: Infinity });
assert.deepEqual(config.currentWallpapers, ['Beach']);
assert.equal(config.serverWidget.pingFrequency, 5);
assert.equal(config.serverWidget.servers.length, 1);
assert.equal(config.clock.size, 'medium');
assert.equal(config.wallpaperBlur, 0);
const defaults = normalizeConfig(null);
defaults.clock.enabled = false;
defaults.currentWallpapers.length = 0;
assert.equal(DEFAULT_CONFIG.clock.enabled, true);
assert.deepEqual(DEFAULT_CONFIG.currentWallpapers, ['Beach']);
storage.set('categories', 'null');
assert.equal(ConfigurationService.loadCategories()[0].name, 'Search');
assert.deepEqual(normalizeCategories([]), []);
assert.deepEqual(normalizeCategories([null, { id: 'c', name: 'Group', websites: [null, { id: 'w', name: 'Site', url: 'https://example.com', categoryId: 'wrong' }] }])[0].websites[0].categoryId, 'c');
storage.set('userWallpapers', '{}');
assert.deepEqual(ConfigurationService.loadUserWallpapers(), []);
});
await test('imports validate before writing, roll back failures, and preserve omitted settings', async () => {
await assert.rejects(ConfigurationService.importConfig({ size: 10 * 1024 * 1024 + 1, text: () => assert.fail('Oversized files must not be read into memory') }), /10MB/);
storage.clear();
storage.set('config', JSON.stringify({ title: 'Original' }));
storage.set('categories', '[]');
await assert.rejects(ConfigurationService.importConfig(file({ config: { title: 'Bad' }, categories: {} })), /Invalid categories/);
assert.equal(JSON.parse(storage.get('config')).title, 'Original');
failWrite = 'categories';
await assert.rejects(ConfigurationService.importConfig(file({ config: { title: 'Bad' }, categories: [] })), /Quota/);
assert.equal(JSON.parse(storage.get('config')).title, 'Original');
const partial = await ConfigurationService.importConfig(file({ categories: '[]' }));
assert.equal(partial.config.title, 'Original');
const legacy = await ConfigurationService.importConfig(file({ config: JSON.stringify({ title: 'Legacy', clock: { enabled: false } }) }));
assert.equal(legacy.config.title, 'Legacy');
assert.equal(legacy.config.clock.enabled, false);
assert.equal(legacy.config.clock.size, 'medium');
storage.set('config', JSON.stringify(normalizeConfig({ title: 'Original'.repeat(50) })));
storage.set('userWallpapers', '[]');
const previous = new Map(storage);
maximumStorageBytes = [...storage.values()].reduce((bytes, value) => bytes + value.length, 0) + 32;
try {
await assert.rejects(ConfigurationService.importConfig(file({ config: { title: 'Small' }, categories: [{ id: 'c', name: 'Group'.repeat(40), websites: [] }], userWallpapers: [{ name: 'Large', url: `https://example.com/${'image'.repeat(200)}` }] })), /Quota/);
assert.deepEqual(storage, previous);
} finally { maximumStorageBytes = Infinity; }
});
await test('wallpaper collisions and non-image uploads cannot overwrite storage', async () => {
await assert.rejects(ConfigurationService.addWallpaper('Beach', 'https://example.com/image.png'), /already exists/);
await assert.rejects(ConfigurationService.addWallpaper('vision-start:icon:reserved', 'https://example.com/image.png'), /reserved/);
await assert.rejects(ConfigurationService.addWallpaperFile(new File(['text'], 'image.png', { type: 'text/plain' })), /image file/);
});
await test('server probes reject network failures, timeouts, and cancellation', async () => {
try {
globalThis.fetch = async (_url, options) => {
assert.equal(options.method, 'HEAD');
assert.equal(options.credentials, 'omit');
return new Response(null);
};
assert.equal(typeof await ping('localhost:8080'), 'number');
globalThis.fetch = async () => { throw new Error('Network failure'); };
await assert.rejects(ping('https://offline.example'), /Network failure/);
await assert.rejects(ping('ftp://example.com'), /HTTP or HTTPS/);
globalThis.fetch = async (_url, { signal }) => new Promise((_resolve, reject) => {
if (signal.aborted) reject(new Error('Aborted'));
else signal.addEventListener('abort', () => reject(new Error('Aborted')), { once: true });
});
const controller = new AbortController();
const request = ping('https://slow.example', 1, controller.signal);
controller.abort();
await assert.rejects(request, /Aborted/);
const originalTimeout = globalThis.setTimeout;
globalThis.setTimeout = callback => { queueMicrotask(callback); return 0; };
try { await assert.rejects(ping('https://slow.example'), /Aborted/); }
finally { globalThis.setTimeout = originalTimeout; }
} finally { globalThis.fetch = originalFetch; }
});
await test('icon requests deduplicate, cap concurrency, reject oversized streams, and evict by bytes', async () => {
let active = 0;
let maximum = 0;
let requests = 0;
try {
globalThis.fetch = async () => {
requests++;
maximum = Math.max(maximum, ++active);
await new Promise(resolve => setTimeout(resolve, 5));
active--;
return new Response(new Uint8Array(200 * 1024), { headers: { 'content-type': 'image/png' } });
};
const results = await Promise.all(Array.from({ length: 12 }, (_, index) => icons.cacheWebsiteIcon(iconUrl(`site${index}`))));
assert(results.every(result => result?.startsWith('data:image/png')));
assert(maximum <= 4);
const reads = chromeReads;
await icons.getCachedWebsiteIcon(iconUrl('site0'));
assert(chromeReads > reads, 'The oldest large icon must leave the extra memory cache');
const before = requests;
await Promise.all([icons.cacheWebsiteIcon(iconUrl('shared')), icons.cacheWebsiteIcon(iconUrl('shared'))]);
assert.equal(requests - before, 1);
let cancelled = false;
globalThis.fetch = async () => new Response(new ReadableStream({
pull(controller) { controller.enqueue(new Uint8Array(128 * 1024)); },
cancel() { cancelled = true; },
}), { headers: { 'content-type': 'image/png' } });
assert.equal(await icons.cacheWebsiteIcon(iconUrl('oversized')), null);
assert.equal(cancelled, true);
} finally { globalThis.fetch = originalFetch; }
});
await test('production UI survives malformed data, distinguishes server status, and releases modal memory', async () => {
const server = await preview({ logLevel: 'silent', preview: { host: '127.0.0.1', port: 0, strictPort: true } });
const browser = await chromium.launch();
try {
const address = server.httpServer.address();
const baseUrl = `http://127.0.0.1:${address.port}`;
const context = await browser.newContext();
const errors = [];
const page = await context.newPage();
await page.clock.install();
page.on('pageerror', error => errors.push(error.message));
await context.addInitScript(() => {
localStorage.setItem('config', JSON.stringify({ currentWallpapers: [], serverWidget: { enabled: true, pingFrequency: 5, servers: [null, { id: 'on', name: 'Online', address: 'https://online.example' }, { id: 'off', name: 'Offline', address: 'https://offline.example' }] } }));
localStorage.setItem('categories', JSON.stringify([null, { id: 'c', name: 'Group', websites: [{ id: 'a', name: 'Alpha', url: 'https://example.com', icon: '' }, { id: 'b', name: 'Beta', url: 'https://example.org', icon: '' }] }]));
localStorage.setItem('userWallpapers', 'null');
});
let onlineProbes = 0;
await context.route('https://online.example/**', route => {
onlineProbes++;
return route.fulfill({ status: 200, body: '' });
});
await context.route('https://offline.example/**', route => route.abort('connectionfailed'));
const metadata = Object.fromEntries(Array.from({ length: 4000 }, (_, index) => [`service-${index}`, { base: 'svg', colors: { blue: `service-${index}-blue` }, aliases: ['unused'.repeat(100)], update: { author: { name: 'unused'.repeat(100) } } }]));
let metadataLoads = 0;
let delayMetadata = false;
let releaseMetadata = () => {};
await context.route('**/icon-metadata.json', async route => {
metadataLoads++;
if (delayMetadata) await new Promise(resolve => { releaseMetadata = resolve; });
return route.fulfill({ json: metadata });
});
await context.route('https://cdn.jsdelivr.net/**', route => route.abort());
await page.goto(baseUrl);
await page.getByRole('link', { name: 'Open Alpha' }).waitFor();
await page.waitForFunction(() => [...document.querySelectorAll('.liquid-status-dot')].some(dot => dot.classList.contains('bg-green-400')) && [...document.querySelectorAll('.liquid-status-dot')].some(dot => dot.classList.contains('bg-red-400')));
const beforeHidden = onlineProbes;
const clockBeforeHidden = await page.locator('.liquid-clock-text').textContent();
await page.evaluate(() => {
Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'hidden' });
document.dispatchEvent(new Event('visibilitychange'));
});
await page.clock.fastForward(120_000);
assert.equal(onlineProbes, beforeHidden);
assert.equal(await page.locator('.liquid-clock-text').textContent(), clockBeforeHidden);
const resumedProbe = page.waitForRequest('https://online.example/');
await page.evaluate(() => {
Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'visible' });
document.dispatchEvent(new Event('visibilitychange'));
});
await resumedProbe;
await page.waitForFunction(before => document.querySelector('.liquid-clock-text').textContent !== before, clockBeforeHidden);
await page.waitForFunction(() => [...document.querySelectorAll('.liquid-status-dot')].some(dot => dot.classList.contains('bg-green-400')));
const downloaded = [];
page.on('request', request => downloaded.push(request.url()));
await page.getByRole('button', { name: 'Open configuration' }).click();
await page.getByRole('dialog').waitFor();
assert(!downloaded.some(url => url.includes('ServerWidgetTab.js')));
await page.getByRole('button', { name: 'Server Widget', exact: true }).click();
await page.getByPlaceholder('Server Name').waitFor();
assert(downloaded.some(url => url.includes('ServerWidgetTab.js')));
await page.getByRole('button', { name: 'Cancel', exact: true }).click();
await page.getByRole('dialog').waitFor({ state: 'hidden' });
await page.getByRole('button', { name: 'Edit page' }).click();
await page.getByRole('button', { name: 'Move Beta left' }).click();
assert.deepEqual(await page.locator('a[aria-label$="edit controls"]').evaluateAll(links => links.map(link => [...link.querySelectorAll('span')].at(-1).textContent)), ['Beta', 'Alpha']);
const openPicker = async () => {
await page.getByRole('button', { name: 'Edit Alpha', exact: true }).click();
const input = page.getByPlaceholder('Icon URL or name');
await input.fill('service');
await input.focus();
await page.locator('.liquid-dropdown-list').waitFor();
assert.equal(await page.locator('.liquid-dropdown-list img').count(), 50);
await page.getByRole('button', { name: 'Close', exact: true }).click();
await page.getByPlaceholder('Icon URL or name').waitFor({ state: 'hidden' });
};
await openPicker();
const session = await context.newCDPSession(page);
const heap = async () => {
await session.send('HeapProfiler.collectGarbage');
return (await session.send('Runtime.getHeapUsage')).usedSize;
};
const before = await heap();
for (let cycle = 0; cycle < 15; cycle++) await openPicker();
const growth = (await heap()) - before;
assert(growth < 2 * 1024 * 1024, `Retained JS heap grew by ${growth} bytes`);
assert.equal(metadataLoads, 16);
await page.evaluate(() => {
const original = window.fetch;
window.fetch = (url, options) => {
if (String(url).endsWith('/icon-metadata.json')) window.metadataSignal = options.signal;
return original(url, options);
};
});
delayMetadata = true;
await page.getByRole('button', { name: 'Edit Alpha', exact: true }).click();
const pendingMetadata = page.waitForRequest('**/icon-metadata.json');
await page.getByPlaceholder('Icon URL or name').focus();
await pendingMetadata;
await page.getByRole('button', { name: 'Close', exact: true }).click();
await page.waitForFunction(() => window.metadataSignal?.aborted === true);
releaseMetadata();
delayMetadata = false;
await openPicker();
assert.equal(metadataLoads, 18);
assert.deepEqual(errors, []);
console.log(`Retained JS heap change after 15 picker cycles: ${Math.round(growth / 1024)} KiB`);
await context.close();
} finally {
await browser.close();
await new Promise(resolve => server.httpServer.close(resolve));
}
});
await test('unpacked MV3 extension loads its new-tab page with working chrome storage', async () => {
const temporary = await mkdtemp(resolve(tmpdir(), 'vision-start-check-'));
const extension = resolve(temporary, 'extension');
let context;
try {
await cp(resolve('dist'), extension, { recursive: true });
await cp(resolve('manifest.json'), resolve(extension, 'manifest.json'));
await cp(resolve('extension'), resolve(extension, 'extension'), { recursive: true });
context = await chromium.launchPersistentContext(resolve(temporary, 'profile'), {
channel: 'chromium',
headless: true,
args: [`--disable-extensions-except=${extension}`, `--load-extension=${extension}`],
});
await context.route(/^https?:/, route => route.abort());
const page = await context.newPage();
const errors = [];
page.on('pageerror', error => errors.push(error.message));
await page.goto('chrome://newtab');
await page.getByRole('button', { name: 'Open configuration' }).waitFor();
const origin = await page.evaluate(() => document.location.origin);
assert(origin.startsWith('chrome-extension://'), `Unexpected new-tab origin: ${origin}`);
assert.equal(await page.evaluate(async () => {
await chrome.storage.local.set({ regression: 'working' });
return (await chrome.storage.local.get('regression')).regression;
}), 'working');
await page.getByRole('button', { name: 'Open configuration' }).click();
await page.getByRole('dialog').waitFor();
assert.deepEqual(errors, []);
} finally {
await context?.close();
await rm(temporary, { recursive: true, force: true });
}
});