|
| 1 | +import { chromium } from 'playwright'; |
| 2 | +import fs from 'fs'; |
| 3 | +import path from 'path'; |
| 4 | +import os from 'os'; |
| 5 | + |
| 6 | +const SERVER_PORT = Number.parseInt(process.env.SERVER_PORT || '8080', 10); |
| 7 | +const SERVER_URL = process.env.SERVER_URL || `http://localhost:${SERVER_PORT}`; |
| 8 | +const LOAD_TIMEOUT = Number.parseInt(process.env.LOAD_TIMEOUT || '30000', 10); |
| 9 | +const LOGO_TIMEOUT = Number.parseInt(process.env.LOGO_TIMEOUT || '30000', 10); |
| 10 | +const DEFAULT_LIST_PATH = path.join(import.meta.dirname, '..', 'test', 'logo-files.txt'); |
| 11 | +const LIST_PATH = process.env.FILE_LIST || process.argv[2] || DEFAULT_LIST_PATH; |
| 12 | + |
| 13 | +function normalizeListEntry(raw) { |
| 14 | + if (!raw) return null; |
| 15 | + let value = raw.trim(); |
| 16 | + if (!value || value.startsWith('#')) return null; |
| 17 | + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { |
| 18 | + value = value.slice(1, -1).trim(); |
| 19 | + } |
| 20 | + if (!value) return null; |
| 21 | + if (value === '~') value = os.homedir(); |
| 22 | + else if (value.startsWith('~/')) value = path.join(os.homedir(), value.slice(2)); |
| 23 | + if (!path.isAbsolute(value)) value = path.resolve(process.cwd(), value); |
| 24 | + return value; |
| 25 | +} |
| 26 | + |
| 27 | +function loadFileList(listPath) { |
| 28 | + if (!fs.existsSync(listPath)) { |
| 29 | + throw new Error(`File list not found: ${listPath}`); |
| 30 | + } |
| 31 | + const lines = fs.readFileSync(listPath, 'utf8').split(/\r?\n/); |
| 32 | + const files = lines.map(normalizeListEntry).filter(Boolean); |
| 33 | + if (files.length === 0) { |
| 34 | + throw new Error(`File list is empty: ${listPath}`); |
| 35 | + } |
| 36 | + return files; |
| 37 | +} |
| 38 | + |
| 39 | +function assertFilesExist(files) { |
| 40 | + const missing = files.filter((filePath) => !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()); |
| 41 | + if (missing.length) { |
| 42 | + throw new Error(`Missing files:\n${missing.join('\n')}`); |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +function getDocType(ext) { |
| 47 | + if (['xlsx', 'xls', 'ods', 'csv'].includes(ext)) return 'cell'; |
| 48 | + if (['docx', 'doc', 'odt', 'txt', 'rtf', 'html'].includes(ext)) return 'word'; |
| 49 | + if (['pptx', 'ppt', 'odp'].includes(ext)) return 'slide'; |
| 50 | + return 'slide'; |
| 51 | +} |
| 52 | + |
| 53 | +function buildOfflineLoaderUrl(filePath) { |
| 54 | + const ext = path.extname(filePath).slice(1).toLowerCase(); |
| 55 | + const doctype = getDocType(ext); |
| 56 | + const convertUrl = `${SERVER_URL}/api/convert?filepath=${encodeURIComponent(filePath)}`; |
| 57 | + const params = new URLSearchParams({ |
| 58 | + url: convertUrl, |
| 59 | + title: path.basename(filePath), |
| 60 | + filepath: filePath, |
| 61 | + filetype: ext, |
| 62 | + doctype, |
| 63 | + }); |
| 64 | + return `${SERVER_URL}/offline-loader-proper.html?${params.toString()}`; |
| 65 | +} |
| 66 | + |
| 67 | +async function assertServer() { |
| 68 | + const response = await fetch(`${SERVER_URL}/healthcheck`).catch(() => null); |
| 69 | + if (!response || !response.ok) { |
| 70 | + throw new Error(`Server not running at ${SERVER_URL}. Start with: bun run server`); |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +async function assertLogoVisible(page, filePath) { |
| 75 | + const url = buildOfflineLoaderUrl(filePath); |
| 76 | + await page.goto(url, { waitUntil: 'load', timeout: LOAD_TIMEOUT }); |
| 77 | + await page.waitForFunction(() => { |
| 78 | + const iframe = document.querySelector('iframe[id*="placeholder"]') || |
| 79 | + document.querySelector('iframe[id*="frameEditor"]') || |
| 80 | + document.querySelector('iframe'); |
| 81 | + if (!iframe || !iframe.contentDocument) return false; |
| 82 | + |
| 83 | + const doc = iframe.contentDocument; |
| 84 | + const logo = doc.querySelector('#oo-desktop-logo') || |
| 85 | + doc.querySelector('#box-document-title .extra img[src*="header-logo_s.svg"]') || |
| 86 | + doc.querySelector('#box-document-title img[src*="header-logo_s.svg"]'); |
| 87 | + if (!logo) return false; |
| 88 | + |
| 89 | + const style = doc.defaultView.getComputedStyle(logo); |
| 90 | + const rect = logo.getBoundingClientRect(); |
| 91 | + return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0; |
| 92 | + }, { timeout: LOGO_TIMEOUT }); |
| 93 | +} |
| 94 | + |
| 95 | +async function run() { |
| 96 | + await assertServer(); |
| 97 | + const files = loadFileList(LIST_PATH); |
| 98 | + assertFilesExist(files); |
| 99 | + |
| 100 | + const browser = await chromium.launch({ headless: true }); |
| 101 | + const page = await browser.newPage({ viewport: { width: 1400, height: 900 } }); |
| 102 | + |
| 103 | + try { |
| 104 | + for (const filePath of files) { |
| 105 | + await assertLogoVisible(page, filePath); |
| 106 | + console.log(`PASS logo visible: ${filePath}`); |
| 107 | + } |
| 108 | + console.log(`Logo header check passed for ${files.length} files`); |
| 109 | + await browser.close(); |
| 110 | + process.exit(0); |
| 111 | + } catch (err) { |
| 112 | + await browser.close(); |
| 113 | + console.error('Logo header check failed'); |
| 114 | + console.error(err && err.message ? err.message : err); |
| 115 | + process.exit(1); |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +run().catch((err) => { |
| 120 | + console.error(err && err.message ? err.message : err); |
| 121 | + process.exit(1); |
| 122 | +}); |
0 commit comments