chore(scripts): 新增共创网络灌数据脚本 + 截图工具支持只截现有作品
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
seed-game-distribution-co-creation.mjs: 走真实 API 建树/复用(标题判重、不绑账号)、封面按树分色系、主题与收藏、断言血缘
This commit is contained in:
@@ -503,7 +503,210 @@ async function loadPlaywright() {
|
||||
return requireFromDir('playwright');
|
||||
}
|
||||
|
||||
/// 「只截图、不造树」模式:给已有作品(例如 scripts/seed-game-distribution-co-creation.mjs 灌的共创网络)
|
||||
/// 出验收截图。设 `E2E_CAPTURE_LINEAGE_URL` 即进入该模式——不注册账号、不建作品、不下架,纯 GET + 截图。
|
||||
async function captureExistingUrls() {
|
||||
const lineageUrl = (process.env.E2E_CAPTURE_LINEAGE_URL ?? '').trim();
|
||||
const themeUrl = (process.env.E2E_CAPTURE_THEME_URL ?? '').trim();
|
||||
const selectTitle = (process.env.E2E_CAPTURE_SELECT_TITLE ?? '').trim();
|
||||
// 视口可配:全树总览常常比 960 高,给个高的窗口才能真的「一眼看全」。
|
||||
const sizeMatch = /^(\d{3,4})x(\d{3,4})$/u.exec(
|
||||
(process.env.E2E_CAPTURE_WINDOW ?? '').trim(),
|
||||
);
|
||||
const desktopViewport = sizeMatch
|
||||
? { width: Number(sizeMatch[1]), height: Number(sizeMatch[2]) }
|
||||
: { width: 1440, height: 960 };
|
||||
const { chromium } = await loadPlaywright();
|
||||
const executablePath = (process.env.E2E_CHROMIUM_EXECUTABLE ?? '').trim();
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
...(executablePath ? { executablePath } : {}),
|
||||
});
|
||||
const shotDir = await mkdtemp(
|
||||
path.join(tmpdir(), 'genarrative-co-creation-visual-'),
|
||||
);
|
||||
const shots = [];
|
||||
const take = async (target, name) => {
|
||||
const file = path.join(shotDir, name);
|
||||
await target.screenshot({ path: file, fullPage: false });
|
||||
shots.push(file);
|
||||
console.log(`[lineage-visual] 截图 ${file}`);
|
||||
return file;
|
||||
};
|
||||
try {
|
||||
// 封面是远端资源:等所有 <img> 落地再截,否则会出现灰块假象。
|
||||
const waitImages = async (target) => {
|
||||
await target
|
||||
.waitForFunction(
|
||||
() =>
|
||||
[...document.images].every(
|
||||
(img) => img.complete && img.naturalWidth > 0,
|
||||
),
|
||||
undefined,
|
||||
{ timeout: 60_000 },
|
||||
)
|
||||
.catch(() => {});
|
||||
await target.waitForTimeout(600);
|
||||
};
|
||||
// ---------- 桌面:全树总览 ----------
|
||||
const desktop = await browser.newContext({
|
||||
viewport: desktopViewport,
|
||||
});
|
||||
const page = await desktop.newPage();
|
||||
await page.goto(lineageUrl, { waitUntil: 'commit', timeout: 120_000 });
|
||||
await page.waitForFunction(
|
||||
() => document.querySelectorAll('.game-lineage-node').length >= 3,
|
||||
undefined,
|
||||
{ timeout: 120_000 },
|
||||
);
|
||||
await page.waitForTimeout(1400);
|
||||
await waitImages(page);
|
||||
const nodeCount = await page.locator('.game-lineage-node').count();
|
||||
check(
|
||||
'截图模式:/games/lineage 已渲染多代节点',
|
||||
nodeCount >= 3,
|
||||
`nodes=${nodeCount} url=${lineageUrl}`,
|
||||
);
|
||||
// 首屏视野契约(与 lineage 工具同口径):要么真的全览,要么按可读缩放下限 + 提示。
|
||||
const fit = await page.evaluate(() => {
|
||||
const canvas = document
|
||||
.querySelector('.game-lineage-canvas')
|
||||
?.getBoundingClientRect();
|
||||
if (!canvas) return { allInside: false, hint: false };
|
||||
const allInside = [
|
||||
...document.querySelectorAll('.game-lineage-node'),
|
||||
].every((node) => {
|
||||
const box = node.getBoundingClientRect();
|
||||
return (
|
||||
box.left >= canvas.left - 2 &&
|
||||
box.top >= canvas.top - 2 &&
|
||||
box.right <= canvas.right + 2 &&
|
||||
box.bottom <= canvas.bottom + 2
|
||||
);
|
||||
});
|
||||
return {
|
||||
allInside,
|
||||
hint: [...document.querySelectorAll('*')].some((element) =>
|
||||
(element.textContent ?? '').includes('已按可读缩放显示'),
|
||||
),
|
||||
};
|
||||
});
|
||||
check(
|
||||
'截图模式:首屏要么全树在画布内,要么给出「已按可读缩放显示」提示',
|
||||
fit.allInside || fit.hint,
|
||||
`allInside=${fit.allInside} hint=${fit.hint} 视口=${desktopViewport.width}x${desktopViewport.height}`,
|
||||
);
|
||||
await take(page, '01-lineage-overview.png');
|
||||
|
||||
// ---------- 桌面:选中态(信息面板 + 面包屑) ----------
|
||||
// 注意:卡片主体是「打开详情」按钮(stopPropagation),点它会跳走;要「选中/锁定」得点卡片**内边距**(6,6)。
|
||||
const card = selectTitle
|
||||
? page
|
||||
.locator('.game-lineage-node')
|
||||
.filter({ hasText: selectTitle })
|
||||
.first()
|
||||
: page.locator('.game-lineage-node').nth(1);
|
||||
let selected = false;
|
||||
if ((await card.count()) > 0) {
|
||||
await card.click({ position: { x: 6, y: 6 } });
|
||||
selected = true;
|
||||
} else {
|
||||
await page
|
||||
.locator('.game-lineage-node')
|
||||
.nth(1)
|
||||
.click({ position: { x: 6, y: 6 } });
|
||||
selected = true;
|
||||
}
|
||||
if (selected) {
|
||||
await page
|
||||
.locator('.game-lineage-panel')
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 30_000 });
|
||||
await page.waitForTimeout(700);
|
||||
const crumbs = await page
|
||||
.locator('.game-lineage-breadcrumb__crumb')
|
||||
.count();
|
||||
check(
|
||||
'截图模式:选中节点后出现信息面板与面包屑',
|
||||
crumbs >= 1,
|
||||
`面包屑=${crumbs}`,
|
||||
);
|
||||
await waitImages(page);
|
||||
await take(page, '02-lineage-selected.png');
|
||||
}
|
||||
|
||||
// ---------- 窄屏 ----------
|
||||
const mobile = await browser.newContext({
|
||||
viewport: { width: 390, height: 844 },
|
||||
});
|
||||
const mobilePage = await mobile.newPage();
|
||||
await mobilePage.goto(lineageUrl, {
|
||||
waitUntil: 'commit',
|
||||
timeout: 120_000,
|
||||
});
|
||||
await mobilePage.waitForFunction(
|
||||
() => document.querySelectorAll('.game-lineage-node').length >= 3,
|
||||
undefined,
|
||||
{ timeout: 120_000 },
|
||||
);
|
||||
await mobilePage.waitForTimeout(1400);
|
||||
// 窄屏首次进入会有「移动端仅支持作品展示」的欢迎弹窗(未 dismiss 时弹):先点掉再截图,
|
||||
// 否则截出来只剩一个遮罩弹窗,看不到窄屏族谱本身。
|
||||
const welcomeOk = mobilePage.getByRole('button', {
|
||||
name: '好',
|
||||
exact: true,
|
||||
});
|
||||
if ((await welcomeOk.count()) > 0) {
|
||||
await welcomeOk.first().click();
|
||||
await mobilePage.waitForTimeout(800);
|
||||
}
|
||||
await waitImages(mobilePage);
|
||||
const narrow = await mobilePage.evaluate(() => ({
|
||||
canvas: Boolean(document.querySelector('.game-lineage-canvas')),
|
||||
nodes: document.querySelectorAll('.game-lineage-node').length,
|
||||
overflow: document.documentElement.scrollWidth - window.innerWidth > 1,
|
||||
}));
|
||||
check(
|
||||
'截图模式:窄屏下族谱画布可见、节点已渲染且页面不横向溢出',
|
||||
narrow.canvas && narrow.nodes >= 3 && !narrow.overflow,
|
||||
`canvas=${narrow.canvas} nodes=${narrow.nodes} overflow=${narrow.overflow}`,
|
||||
);
|
||||
await take(mobilePage, '03-lineage-narrow.png');
|
||||
await mobile.close();
|
||||
|
||||
// ---------- 主题页 ----------
|
||||
if (themeUrl) {
|
||||
const themePage = await desktop.newPage();
|
||||
await themePage.goto(themeUrl, { waitUntil: 'commit', timeout: 120_000 });
|
||||
await themePage
|
||||
.locator('.game-theme-root')
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 60_000 });
|
||||
await themePage.waitForTimeout(1000);
|
||||
await waitImages(themePage);
|
||||
const roots = await themePage.locator('.game-theme-root').count();
|
||||
check('截图模式:主题页已渲染作品树', roots >= 1, `roots=${roots}`);
|
||||
await take(themePage, '04-theme-page.png');
|
||||
void themePage;
|
||||
}
|
||||
await desktop.close();
|
||||
console.log(`[lineage-visual] 截图目录=${shotDir}`);
|
||||
console.log(`[lineage-visual] 结果:check=${checks} fail=${failures}`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
return shots;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if ((process.env.E2E_CAPTURE_LINEAGE_URL ?? '').trim()) {
|
||||
await captureExistingUrls();
|
||||
const failed = failures > 0;
|
||||
console.log(
|
||||
`[lineage-visual] 只用现有作品截图:check=${checks} fail=${failures}`,
|
||||
);
|
||||
process.exit(failed ? 1 : 0);
|
||||
}
|
||||
console.log(`[lineage-visual] api-server=${API} web=${WEB}`);
|
||||
const stamp = Date.now();
|
||||
const suffix = String(stamp).slice(-6);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user