71 lines
4.3 KiB
JavaScript
71 lines
4.3 KiB
JavaScript
'use strict';
|
|
// Disposable local reader + browser; no production accounts or scraper traffic.
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs'), os = require('node:os'), path = require('node:path'), net = require('node:net');
|
|
const { spawn } = require('node:child_process');
|
|
const { chromium } = require('playwright');
|
|
const root = path.resolve(__dirname, '..');
|
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
async function freePort() {
|
|
const server = net.createServer();
|
|
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
const port = server.address().port;
|
|
await new Promise(resolve => server.close(resolve));
|
|
return port;
|
|
}
|
|
|
|
(async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mc-greatest-ui-'));
|
|
const library = path.join(dir, 'library'), data = path.join(dir, 'data');
|
|
fs.mkdirSync(library); fs.mkdirSync(data);
|
|
const port = await freePort(), base = `http://127.0.0.1:${port}`;
|
|
const child = spawn(process.execPath, ['server.js'], { cwd: root, windowsHide: true,
|
|
env: { ...process.env, PORT: String(port), LIBRARY: library, DATA: data, SCAN_INTERVAL: '0',
|
|
FULL_SCAN_INTERVAL: '0', SCRAPER_URL: 'http://127.0.0.1:1', COOKIE_SECURE: '0' }, stdio: 'ignore' });
|
|
let browser;
|
|
try {
|
|
for (let i = 0; i < 80; i++) {
|
|
if (await fetch(base + '/health').then(r => r.ok).catch(() => false)) break;
|
|
await delay(100);
|
|
}
|
|
assert.equal((await fetch(base + '/health')).status, 200);
|
|
const signup = await fetch(base + '/api/signup', { method: 'POST', headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ name: 'curator', password: 'local-test-password' }) });
|
|
assert.equal(signup.status, 200);
|
|
const cookie = signup.headers.get('set-cookie').split(';')[0];
|
|
browser = await chromium.launch({ channel: process.env.QA_BROWSER_CHANNEL || 'msedge', headless: true });
|
|
const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
const [name, value] = cookie.split('=');
|
|
await context.addCookies([{ name, value, url: base }]);
|
|
await context.route(/^https:\/\//, route => route.abort()); // Never load remote cover art in fixture.
|
|
const page = await context.newPage(), errors = [];
|
|
page.on('pageerror', error => errors.push(error.message));
|
|
await page.goto(base + '/greatest.html');
|
|
await page.getByRole('heading', { name: 'Shijō Saikō no Manga' }).waitFor();
|
|
assert.equal(await page.getByRole('link', { name: 'Shijō Saikō no Manga' }).textContent(), 'Shijō Saikō no Manga');
|
|
assert.equal(await page.locator('.greatest-period').count(), 13);
|
|
assert.equal(await page.locator('.greatest-tile').count(), 106);
|
|
const astro = page.locator('.greatest-tile').filter({ hasText: 'Astro Boy' });
|
|
await astro.getByRole('button', { name: 'Definitely want: Astro Boy' }).click();
|
|
await page.getByRole('button', { name: '🔥 Flame' }).click();
|
|
assert.equal(await page.locator('.greatest-tile').count(), 1);
|
|
await page.getByRole('button', { name: 'All', exact: true }).click();
|
|
await page.getByRole('button', { name: 'Rank by everyone' }).click();
|
|
await page.getByRole('button', { name: 'Details for Astro Boy' }).click();
|
|
await page.getByRole('dialog').getByText('Community votes: 🔥 1').waitFor();
|
|
await page.getByRole('dialog').getByRole('button', { name: 'Close' }).click();
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
assert.equal(await page.locator('.nav-greatest').evaluate(el => getComputedStyle(el, '::after').content), '"Saikō"');
|
|
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Mobile horizontal overflow');
|
|
await page.getByRole('button', { name: '📚 In library' }).click();
|
|
assert.equal(await page.locator('.greatest-tile').count(), 0);
|
|
assert.deepEqual(errors, []);
|
|
await context.close();
|
|
console.log('Greatest of All Time browser QA passed: periods, ratings, filters, modal, mobile layout.');
|
|
} finally {
|
|
if (browser) await browser.close();
|
|
await new Promise(resolve => { if (child.exitCode !== null) return resolve(); child.once('exit', resolve); child.kill(); });
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
})().catch(error => { console.error(error); process.exitCode = 1; });
|