This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -86,6 +86,10 @@ data/excel_exports/
|
||||
*.xlsx~
|
||||
~$*.xlsx
|
||||
|
||||
# 本機 QA / 頁面快取
|
||||
data/*_cache/
|
||||
data/ai_automation_smoke_history.jsonl
|
||||
|
||||
# 上傳檔案
|
||||
web/static/uploads/
|
||||
web/static/screenshots/
|
||||
|
||||
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.139"
|
||||
SYSTEM_VERSION = "V10.140"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ function parseArgs(argv) {
|
||||
routes: [],
|
||||
viewports: DEFAULT_VIEWPORTS,
|
||||
timeoutMs: 30000,
|
||||
settleMs: 350,
|
||||
maxOverflow: 1,
|
||||
screenshotDir: '',
|
||||
json: false,
|
||||
@@ -126,6 +127,8 @@ function parseArgs(argv) {
|
||||
options.viewports = [];
|
||||
} else if (arg === '--timeout') {
|
||||
options.timeoutMs = parseInt(argv[++i], 10) * 1000;
|
||||
} else if (arg === '--settle-ms') {
|
||||
options.settleMs = parseInt(argv[++i], 10);
|
||||
} else if (arg === '--max-overflow') {
|
||||
options.maxOverflow = parseInt(argv[++i], 10);
|
||||
} else if (arg === '--screenshot-dir') {
|
||||
@@ -159,6 +162,7 @@ Options:
|
||||
--viewport name=WxH Add a viewport
|
||||
--clear-default-viewports Use only custom --viewport entries
|
||||
--timeout SEC Navigation timeout, default 30
|
||||
--settle-ms MS Fixed post-DOM layout settle wait, default 350
|
||||
--max-overflow PX Allowed body overflow, default 1
|
||||
--screenshot-dir DIR Save failure screenshots
|
||||
--json Print JSON summary
|
||||
@@ -276,14 +280,20 @@ async function main() {
|
||||
|
||||
const browser = await chromium.launch(launchOptions);
|
||||
const results = [];
|
||||
const pages = new Map();
|
||||
|
||||
try {
|
||||
for (const viewport of options.viewports) {
|
||||
const page = await browser.newPage({
|
||||
viewport: { width: viewport.width, height: viewport.height },
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
pages.set(viewport.name, page);
|
||||
}
|
||||
|
||||
for (const route of options.routes) {
|
||||
for (const viewport of options.viewports) {
|
||||
const page = await browser.newPage({
|
||||
viewport: { width: viewport.width, height: viewport.height },
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
const page = pages.get(viewport.name);
|
||||
const url = routeToUrl(options.baseUrl, route);
|
||||
let status = 0;
|
||||
let error = '';
|
||||
@@ -291,7 +301,10 @@ async function main() {
|
||||
|
||||
try {
|
||||
const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: options.timeoutMs });
|
||||
await page.waitForLoadState('networkidle', { timeout: Math.min(options.timeoutMs, 5000) }).catch(() => {});
|
||||
await page.evaluate(() => document.fonts && document.fonts.ready).catch(() => {});
|
||||
if (options.settleMs > 0) {
|
||||
await page.waitForTimeout(options.settleMs);
|
||||
}
|
||||
status = response ? response.status() : 0;
|
||||
metrics = await collectMetrics(page, options.maxOverflow);
|
||||
if (new URL(metrics.finalUrl).pathname === '/login') {
|
||||
@@ -315,11 +328,10 @@ async function main() {
|
||||
const file = `${safeName(route)}_${safeName(viewport.name)}.png`;
|
||||
await page.screenshot({ path: path.join(options.screenshotDir, file), fullPage: false });
|
||||
}
|
||||
|
||||
await page.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await Promise.all(Array.from(pages.values()).map((page) => page.close().catch(() => {})));
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -540,19 +540,98 @@
|
||||
}
|
||||
|
||||
.edm-page .campaign-table-wrap::before {
|
||||
content: '左右滑動查看完整商品列表';
|
||||
position: sticky;
|
||||
left: 0;
|
||||
content: none;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table-wrap {
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table,
|
||||
.edm-page .campaign-table tbody,
|
||||
.edm-page .campaign-table tr,
|
||||
.edm-page .campaign-table td {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
max-width: calc(100vw - 28px);
|
||||
margin: 0 0 8px;
|
||||
padding: 6px 9px;
|
||||
color: var(--momo-text-secondary);
|
||||
background: var(--momo-bg-paper);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table {
|
||||
min-width: 0;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table tbody {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table tr {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--momo-border-light);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
border-radius: 8px;
|
||||
background: var(--momo-bg-surface);
|
||||
}
|
||||
|
||||
.edm-page .campaign-table td {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(72px, 0.32fr) minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--momo-border-light);
|
||||
text-align: left !important;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table td:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table td::before {
|
||||
color: var(--momo-text-tertiary);
|
||||
font-family: var(--momo-font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table td:nth-child(1)::before { content: '分類'; }
|
||||
.edm-page .campaign-table td:nth-child(2)::before { content: '商品'; }
|
||||
.edm-page .campaign-table td:nth-child(3)::before { content: '價格'; }
|
||||
.edm-page .campaign-table td:nth-child(4)::before { content: '銷售'; }
|
||||
.edm-page .campaign-table td:nth-child(5)::before { content: '追蹤'; }
|
||||
|
||||
.edm-page .campaign-table td[colspan] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.edm-page .campaign-table td[colspan]::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.edm-page .campaign-product-cell {
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.edm-page .campaign-product-thumb {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.edm-page .campaign-history-button {
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.edm-page .campaign-sales-stack,
|
||||
.edm-page .campaign-track-stack {
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user