hexagone-web-feature-extractor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHexagone Web Feature Extractor
Hexagone Web功能提取器
Explore a Hexagone Web functional space, capture screenshots of every page/tab, and produce a Markdown document (.md) oriented for Product Owners with functional descriptions and embedded screenshots.
探索Hexagone Web功能空间,截取每个页面/标签页的截图,生成面向产品负责人(PO)的Markdown文档(.md),包含功能描述和嵌入的截图。
Prerequisites
前置依赖
- Node.js installed
- Playwright npm package () — installs headless Chromium automatically
npm install playwright - Network access to the Hexagone Web server (default: )
https://ws004202.dedalus.lan:8065/hexagone-01/vue/login
- Node.js 已安装
- Playwright npm包()—— 会自动安装无头Chromium
npm install playwright - 可访问Hexagone Web服务器的网络(默认地址:)
https://ws004202.dedalus.lan:8065/hexagone-01/vue/login
Configuration
配置项
Default values calibrated for the standard Hexagone Web layout at 1920x1080. Adjust if the layout differs.
| Parameter | Default | Description |
|---|---|---|
| Viewport | | Browser viewport size |
| Sidebar click X coordinate | | Horizontal pixel position for sidebar icon clicks (collapsed mode) |
| Sidebar max left boundary | | Max |
| Header height offset | | Min |
| Login wait timeout | | Max time to poll for successful login |
| Page load wait timeout | | Max time to poll for page load after navigation |
| Screenshots directory | | Where screenshots are saved (relative to working directory) |
默认值适配1920x1080分辨率下的标准Hexagone Web布局,若布局不同可自行调整。
| 参数 | 默认值 | 描述 |
|---|---|---|
| Viewport | | 浏览器视口大小 |
| Sidebar click X coordinate | | 点击侧边栏图标的水平像素位置(收起模式下) |
| Sidebar max left boundary | | 用于识别侧边栏链接的最大 |
| Header height offset | | 用于排除头部元素的最小 |
| Login wait timeout | | 轮询等待登录成功的最大时长 |
| Page load wait timeout | | 导航后轮询等待页面加载的最大时长 |
| Screenshots directory | | 截图保存的位置(相对于工作目录) |
Workflow Overview
工作流概览
1. SETUP → Install Playwright, launch headless Chromium
2. CONNECTION → Log in to Hexagone Web
3. NAVIGATION → Navigate to the target space
4. DISCOVERY → Expand sidebar, list all menu pages
5. EXPLORATION → Visit each page, capture screenshots + metadata
6. GENERATION → Produce the Markdown document with embedded screenshotsKey advantage over Chrome extension approach: Screenshots save directly to disk via — no bridge server or transfer step needed.
page.screenshot()1. SETUP → Install Playwright, launch headless Chromium
2. CONNECTION → Log in to Hexagone Web
3. NAVIGATION → Navigate to the target space
4. DISCOVERY → Expand sidebar, list all menu pages
5. EXPLORATION → Visit each page, capture screenshots + metadata
6. GENERATION → Produce the Markdown document with embedded screenshots与Chrome扩展方案相比的核心优势:通过直接将截图保存到磁盘——无需桥接服务器或传输步骤。
page.screenshot()Step 1: Setup
步骤1:环境搭建
1.1 Install Playwright
1.1 安装Playwright
bash
npm install playwright
npx playwright install chromiumbash
npm install playwright
npx playwright install chromium1.2 Launch Browser
1.2 启动浏览器
javascript
const { chromium } = require('playwright');
const browser = await chromium.launch({
headless: true,
args: ['--ignore-certificate-errors', '--no-sandbox']
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
ignoreHTTPSErrors: true // Handles self-signed certs automatically
});
const page = await context.newPage();Why headless Chromium? Eliminates the need for manual SSL certificate acceptance, Chrome extension setup, and screenshot bridge transfers. The option handles self-signed certificates programmatically.
ignoreHTTPSErrors: truejavascript
const { chromium } = require('playwright');
const browser = await chromium.launch({
headless: true,
args: ['--ignore-certificate-errors', '--no-sandbox']
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
ignoreHTTPSErrors: true // Handles self-signed certs automatically
});
const page = await context.newPage();为什么选择无头Chromium? 无需手动接受SSL证书、安装Chrome扩展,也不需要截图桥接传输。选项可自动处理自签名证书。
ignoreHTTPSErrors: trueStep 2: Connection to Hexagone Web
步骤2:连接Hexagone Web
2.1 Navigate to Login Page
2.1 跳转到登录页
javascript
await page.goto(LOGIN_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
await sleep(3000); // Wait for Vue.js to mountjavascript
await page.goto(LOGIN_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
await sleep(3000); // Wait for Vue.js to mount2.2 Fill the Login Form
2.2 填写登录表单
The Hexagone Web login form has 3 fields: Username, Password, Manager code. Default credentials: username with a random password, unless the user provides others.
apvhnUse with the native setter pattern — required for Vue.js which does not detect value changes injected directly:
page.evaluate()javascript
await page.evaluate(({ username, password }) => {
const nativeSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
const userInput = document.querySelector('input[type="text"]');
if (userInput) {
nativeSetter.call(userInput, username);
userInput.dispatchEvent(new Event('input', { bubbles: true }));
}
const pwdInput = document.querySelector('input[type="password"]');
if (pwdInput) {
nativeSetter.call(pwdInput, password);
pwdInput.dispatchEvent(new Event('input', { bubbles: true }));
}
const loginBtn = Array.from(document.querySelectorAll('button'))
.find(b => /connect/i.test(b.textContent));
if (loginBtn) loginBtn.click();
}, { username: USERNAME, password: PASSWORD });Hexagone Web登录表单有3个字段:用户名、密码、管理员代码。默认凭证:用户名,随机密码,用户也可自行提供其他凭证。
apvhn使用搭配原生setter模式——Vue.js必须使用该方式,因为Vue.js无法检测直接注入的值变更:
page.evaluate()javascript
await page.evaluate(({ username, password }) => {
const nativeSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
const userInput = document.querySelector('input[type="text"]');
if (userInput) {
nativeSetter.call(userInput, username);
userInput.dispatchEvent(new Event('input', { bubbles: true }));
}
const pwdInput = document.querySelector('input[type="password"]');
if (pwdInput) {
nativeSetter.call(pwdInput, password);
pwdInput.dispatchEvent(new Event('input', { bubbles: true }));
}
const loginBtn = Array.from(document.querySelectorAll('button'))
.find(b => /connect/i.test(b.textContent));
if (loginBtn) loginBtn.click();
}, { username: USERNAME, password: PASSWORD });2.3 Verify Connection
2.3 验证连接状态
Poll every 2s for up to 30s until the URL no longer contains :
/loginjavascript
for (let i = 0; i < 15; i++) {
await sleep(2000);
if (!page.url().includes('/login')) break;
}If login fails: Take a debug screenshot with and report the failure.
page.screenshot()每2秒轮询一次,最长等待30秒,直到URL不再包含:
/loginjavascript
for (let i = 0; i < 15; i++) {
await sleep(2000);
if (!page.url().includes('/login')) break;
}如果登录失败:调用截取调试用截图并上报失败。
page.screenshot()Step 3: Navigation to the Target Space
步骤3:导航到目标空间
3.1 Open the Space Selector
3.1 打开空间选择器
CRITICAL: Use — NOT via .
page.mouse.click()el.click()page.evaluate()Vue.js event handlers require native mouse events (mousedown + mouseup + click). JavaScript's only dispatches the event and will not trigger the space dropdown. This was the #1 bug found during development.
el.click()clickThe space selector is the with class in the orange breadcrumb bar. It contains an icon followed by a with the current space name.
divbg:orange-dark<i class="hexa-icons">changer_espaces</i><span>javascript
// Find the space selector coordinates
const selectorRect = await page.evaluate(() => {
for (const el of document.querySelectorAll('div, span')) {
const cls = typeof el.className === 'string' ? el.className : '';
if (cls.includes('bg:orange-dark') && !cls.includes('uppercase') && !cls.includes('hover:')) {
const rect = el.getBoundingClientRect();
if (rect.top > 30 && rect.top < 70 && rect.height > 15) {
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}
}
}
return null;
});
// Click with REAL mouse events (mandatory for Vue.js)
await page.mouse.click(selectorRect.x, selectorRect.y);
await sleep(3000);关键注意事项:使用——不要通过调用。
page.mouse.click()page.evaluate()el.click()Vue.js事件处理器需要原生鼠标事件(mousedown + mouseup + click)。JavaScript的只会派发事件,无法触发空间下拉菜单,这是开发过程中发现的头号Bug。
el.click()click空间选择器是橙色面包屑栏中类名为的元素,内部包含图标,后面跟随显示当前空间名称的元素。
bg:orange-darkdiv<i class="hexa-icons">changer_espaces</i><span>javascript
// Find the space selector coordinates
const selectorRect = await page.evaluate(() => {
for (const el of document.querySelectorAll('div, span')) {
const cls = typeof el.className === 'string' ? el.className : '';
if (cls.includes('bg:orange-dark') && !cls.includes('uppercase') && !cls.includes('hover:')) {
const rect = el.getBoundingClientRect();
if (rect.top > 30 && rect.top < 70 && rect.height > 15) {
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}
}
}
return null;
});
// Click with REAL mouse events (mandatory for Vue.js)
await page.mouse.click(selectorRect.x, selectorRect.y);
await sleep(3000);3.2 Select the Space
3.2 选择目标空间
The dropdown renders inside the sidebar area as a list of elements with class . Spaces are listed alphabetically.
<div>px:1 py:3/4 hover:bg:orange-dark cursor:pointerjavascript
// Find the target space element
const target = await page.evaluate((spaceName) => {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
while (walker.nextNode()) {
const el = walker.currentNode;
if (el.textContent.trim() === spaceName) {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0 && rect.top > 30) {
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}
}
}
return null;
}, TARGET_SPACE);
// Click with mouse (not el.click())
await page.mouse.click(target.x, target.y);下拉菜单渲染在侧边栏区域,是类名为的元素列表,空间按字母顺序排列。
px:1 py:3/4 hover:bg:orange-dark cursor:pointer<div>javascript
// Find the target space element
const target = await page.evaluate((spaceName) => {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
while (walker.nextNode()) {
const el = walker.currentNode;
if (el.textContent.trim() === spaceName) {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0 && rect.top > 30) {
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}
}
}
return null;
}, TARGET_SPACE);
// Click with mouse (not el.click())
await page.mouse.click(target.x, target.y);3.3 Wait for Loading
3.3 等待加载完成
Hexagone Web redirects via an intermediate "Connexion... Redirection..." page. Poll every 2s for up to 24s until the URL no longer contains (the default landing space):
patient-portaljavascript
for (let i = 0; i < 12; i++) {
await sleep(2000);
if (!page.url().includes('patient-portal')) break;
}
await sleep(3000); // Extra wait for Vue.js renderingHexagone Web会通过中间的"Connexion... Redirection..."页面跳转。每2秒轮询一次,最长等待24秒,直到URL不再包含(默认落地空间):
patient-portaljavascript
for (let i = 0; i < 12; i++) {
await sleep(2000);
if (!page.url().includes('patient-portal')) break;
}
await sleep(3000); // Extra wait for Vue.js renderingStep 4: Page Discovery
步骤4:页面发现
4.1 Expand the Sidebar
4.1 展开侧边栏
The sidebar is collapsed by default (icons only, width ~65px). Click the hamburger menu to expand it and reveal text labels:
javascript
await page.mouse.click(34, 50); // Hamburger icon position
await sleep(2000);侧边栏默认是收起状态(仅显示图标,宽度约65px)。点击汉堡菜单展开侧边栏,显示文本标签:
javascript
await page.mouse.click(34, 50); // Hamburger icon position
await sleep(2000);4.2 Identify Sidebar Menu Entries
4.2 识别侧边栏菜单条目
Primary method: Look for elements with class in the left 280px. Strip icon text from children:
cursor:pointer<i class="hexa-icons">javascript
const menuItems = await page.evaluate((excludeLabels) => {
const items = [];
const seen = new Set();
const allEls = document.querySelectorAll('[class*="cursor:pointer"], a');
for (const el of allEls) {
const rect = el.getBoundingClientRect();
if (rect.left < 280 && rect.top > 55 && rect.height > 15 && rect.height < 60) {
let text = el.textContent.trim();
// Strip icon prefix text
const icon = el.querySelector('i');
if (icon) text = text.replace(icon.textContent.trim(), '').trim();
if (!text || text.length <= 1 || text.length >= 60 || seen.has(text)) continue;
if (excludeLabels.includes(text)) continue;
// Skip section headers (all-caps short text like "ACHATS")
if (/^[A-Z ]+$/.test(text) && text.length < 15) continue;
seen.add(text);
items.push({
label: text,
y: Math.round(rect.top + rect.height / 2),
x: Math.round(rect.left + rect.width / 2)
});
}
}
return items;
}, ['Trier par Importance', 'Trier par Emetteur']);Sidebar DOM structure (observed):
html
<div class="sidebar--section min-w:sidebar bg:teal-darker">
<div class="py:1 transition cursor:pointer w:inherit hover:bg:teal-dark">
<div class="flex items:center whitespace:no-wrap">
<i class="hexa-icons text:3/2" aria-label="Fournisseurs">fournisseurs</i>
<span class="sidebar--label">Fournisseurs</span>
</div>
</div>
</div>Note: The old selector does NOT work for all spaces. The sidebar elements are elements with class, not tags.
a.hexa<div>cursor:pointer<a>主要方法:查找左侧280px范围内类名包含的元素,剔除子元素的图标文本:
cursor:pointer<i class="hexa-icons">javascript
const menuItems = await page.evaluate((excludeLabels) => {
const items = [];
const seen = new Set();
const allEls = document.querySelectorAll('[class*="cursor:pointer"], a');
for (const el of allEls) {
const rect = el.getBoundingClientRect();
if (rect.left < 280 && rect.top > 55 && rect.height > 15 && rect.height < 60) {
let text = el.textContent.trim();
// Strip icon prefix text
const icon = el.querySelector('i');
if (icon) text = text.replace(icon.textContent.trim(), '').trim();
if (!text || text.length <= 1 || text.length >= 60 || seen.has(text)) continue;
if (excludeLabels.includes(text)) continue;
// Skip section headers (all-caps short text like "ACHATS")
if (/^[A-Z ]+$/.test(text) && text.length < 15) continue;
seen.add(text);
items.push({
label: text,
y: Math.round(rect.top + rect.height / 2),
x: Math.round(rect.left + rect.width / 2)
});
}
}
return items;
}, ['Trier par Importance', 'Trier par Emetteur']);侧边栏DOM结构(实际观测):
html
<div class="sidebar--section min-w:sidebar bg:teal-darker">
<div class="py:1 transition cursor:pointer w:inherit hover:bg:teal-dark">
<div class="flex items:center whitespace:no-wrap">
<i class="hexa-icons text:3/2" aria-label="Fournisseurs">fournisseurs</i>
<span class="sidebar--label">Fournisseurs</span>
</div>
</div>
</div>注意:旧的选择器不适用于所有空间。侧边栏元素是带有类的元素,不是标签。
a.hexacursor:pointer<div><a>4.3 Items to Exclude
4.3 需要排除的条目
Filter out UI elements that are not pages:
- /
Trier par Importance— sort buttons in the Mes Post-Its panelTrier par Emetteur - Section headers (all-caps short text like ) — section dividers, not clickable pages
ACHATS
过滤掉不属于页面的UI元素:
- /
Trier par Importance—— 我的便签面板中的排序按钮Trier par Emetteur - 章节头部(全大写短文本如)—— 章节分隔符,不是可点击的页面
ACHATS
4.4 Validation Gate
4.4 校验关卡
If zero items are found after discovery: Stop the exploration and take a debug screenshot. Do NOT proceed with an empty page list. Save the DOM to a debug file for analysis.
如果发现后页面条目数量为0:停止探索并截取调试截图,不要使用空页面列表继续执行。保存DOM到调试文件用于分析。
4.5 Collapse Sidebar
4.5 收起侧边栏
After discovery, collapse the sidebar before starting exploration:
javascript
await page.mouse.click(34, 50); // Toggle hamburger
await sleep(1000);页面发现完成后,在开始探索前收起侧边栏:
javascript
await page.mouse.click(34, 50); // Toggle hamburger
await sleep(1000);Step 5: Exploration and Capture
步骤5:探索与截图
5.1 For Each Page in the Menu
5.1 遍历菜单中的每个页面
CRITICAL: The sidebar collapses after clicking a menu item. You MUST re-expand the sidebar and re-discover the item's position before each click.
For each menu item:
1. Expand sidebar: page.mouse.click(34, 50), wait 1.5s
2. Re-discover the item position via page.evaluate() (label matching)
3. Click the item: page.mouse.click(freshPos.x, freshPos.y)
4. Wait for page load (networkidle + 1.5s extra)
5. Take screenshot: page.screenshot({ path: ... })
6. Extract metadata via page.evaluate()
7. If tabs found, explore them (see 5.2)
8. Build feature description with PO-oriented textWhy re-discover each time? The sidebar re-renders its content when expanded. Item coordinates shift depending on scroll position and which items are visible. Using stale coordinates from the initial discovery will click on the main content area instead of the sidebar.
关键注意事项:点击菜单项后边栏会自动收起。每次点击前必须重新展开侧边栏,重新获取条目的位置。
For each menu item:
1. Expand sidebar: page.mouse.click(34, 50), wait 1.5s
2. Re-discover the item position via page.evaluate() (label matching)
3. Click the item: page.mouse.click(freshPos.x, freshPos.y)
4. Wait for page load (networkidle + 1.5s extra)
5. Take screenshot: page.screenshot({ path: ... })
6. Extract metadata via page.evaluate()
7. If tabs found, explore them (see 5.2)
8. Build feature description with PO-oriented text为什么每次都要重新发现? 侧边栏展开时会重新渲染内容,条目的坐标会根据滚动位置和可见条目发生变化。使用初始发现时的旧坐标会点击到主内容区域,而不是侧边栏。
5.2 Identify Internal Tabs
5.2 识别内部标签页
Some pages have internal tabs (e.g., Fournisseurs → Saisie / Consultation / Réalisé, Marchés → SAISIE / CONSULTATION / DÉBLOCAGE). Use for tabs too:
page.mouse.click()javascript
const tabCoords = await page.evaluate((label) => {
for (const tab of document.querySelectorAll('[role="tab"], .v-tab')) {
if (tab.textContent.trim() === label) {
const rect = tab.getBoundingClientRect();
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}
}
return null;
}, tabLabel);
if (tabCoords) {
await page.mouse.click(tabCoords.x, tabCoords.y);
await sleep(2500);
await page.screenshot({ path: tabScreenshotPath });
}Warning: The selector is too broad — it matches pagination controls, filter dropdowns, and elements from previously rendered pages. Prefer or for tab detection.
[class*="tab"][role="tab"].v-tab部分页面有内部标签(例如:供应商 → 录入 / 查询 / 已完成,采购 → 录入 / 查询 / 解锁)。标签页也需要使用操作:
page.mouse.click()javascript
const tabCoords = await page.evaluate((label) => {
for (const tab of document.querySelectorAll('[role="tab"], .v-tab')) {
if (tab.textContent.trim() === label) {
const rect = tab.getBoundingClientRect();
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
}
}
return null;
}, tabLabel);
if (tabCoords) {
await page.mouse.click(tabCoords.x, tabCoords.y);
await sleep(2500);
await page.screenshot({ path: tabScreenshotPath });
}警告:选择器范围过宽——会匹配分页控件、筛选下拉框以及之前渲染页面的残留元素。推荐使用或来检测标签页。
[class*="tab"][role="tab"].v-tab5.3 Take Screenshots
5.3 截取截图
Screenshots save directly to disk — no bridge or transfer needed:
javascript
await page.screenshot({
path: path.join(SCREENSHOT_DIR, `${index}-${sanitize(pageName)}.png`),
fullPage: false // Capture viewport only (1920x1080)
});截图直接保存到磁盘——无需桥接或传输步骤:
javascript
await page.screenshot({
path: path.join(SCREENSHOT_DIR, `${index}-${sanitize(pageName)}.png`),
fullPage: false // Capture viewport only (1920x1080)
});5.4 Store Metadata
5.4 存储元数据
Build a array during exploration with PO-oriented descriptions enriched from page content analysis (tables, forms, actions, tabs).
features.json探索过程中构建数组,包含面向PO的功能描述,通过页面内容分析(表格、表单、操作、标签页)补充信息。
features.jsonStep 6: Markdown Document Generation
步骤6:Markdown文档生成
6.1 Prepare the Input
6.1 准备输入数据
Create a file from the metadata collected in Step 5. The file must conform to this structure:
features.jsonjson
{
"space": "Name of the explored space",
"features": [
{
"title": "Feature name (string, required)",
"description": "PO-oriented functional description (string, required)",
"capabilities": ["Capability 1", "Capability 2"],
"businessValue": "Business value description (string)",
"screenshots": [
{ "file": "filename.png", "caption": "Screenshot description" }
]
}
]
}Validation rules:
- must be a non-empty string
space - must be a non-empty array
features - Each feature must have (string) and
title(string)description - must be an array of strings (can be empty)
capabilities - must be an array of objects with
screenshots(string) andfile(string)caption
根据步骤5收集的元数据创建文件,文件必须符合以下结构:
features.jsonjson
{
"space": "Name of the explored space",
"features": [
{
"title": "Feature name (string, required)",
"description": "PO-oriented functional description (string, required)",
"capabilities": ["Capability 1", "Capability 2"],
"businessValue": "Business value description (string)",
"screenshots": [
{ "file": "filename.png", "caption": "Screenshot description" }
]
}
]
}校验规则:
- 必须为非空字符串
space - 必须为非空数组
features - 每个功能必须包含(字符串)和
title(字符串)description - 必须为字符串数组(可为空)
capabilities - 必须为对象数组,每个对象包含
screenshots(字符串)和file(字符串)caption
6.2 Generate the Document
6.2 生成文档
Use the script :
scripts/generate-md.jsbash
node scripts/generate-md.js --input features.json --output /path/to/output.md --screenshots /path/to/screenshotsThe argument is required. The script validates the input and fails with clear error messages if the JSON is malformed. No needed — the script uses only Node.js built-in modules.
--inputnpm install使用脚本:
scripts/generate-md.jsbash
node scripts/generate-md.js --input features.json --output /path/to/output.md --screenshots /path/to/screenshots--inputnpm6.3 Document Structure
6.3 文档结构
undefinedundefinedTitle (space name)
Title (space name)
Subtitle with date
Subtitle with date
Table of contents (linked)
Table of contents (linked)
For each feature:
N. Feature title
- Functional description
- Screenshot(s) as
Key capabilities (numbered list)
Business value
undefinedFor each feature:
N. Feature title
- Functional description
- Screenshot(s) as
Key capabilities (numbered list)
Business value
undefined6.4 Validation and Output
6.4 校验与输出
Verify the generated file exists and contains all expected features:
bash
grep -c '^## [0-9]' output.md # Should match the number of features验证生成的文件存在且包含所有预期功能:
bash
grep -c '^## [0-9]' output.md # Should match the number of featuresInput Parameters
输入参数
The user must provide:
- Login URL (optional): defaults to . The user can provide a different URL if needed.
https://ws004202.dedalus.lan:8065/hexagone-01/vue/login - Username (optional): defaults to . The user can provide a different code if needed.
apvhn - Password (optional): defaults to a random value. The user can provide a specific password if needed.
- Target space: Exact name of the space to explore (e.g., "HA GHT", "STRUCTURES / NOMENCLATURES")
用户需要提供:
- 登录URL (可选):默认值为,用户可按需提供其他地址
https://ws004202.dedalus.lan:8065/hexagone-01/vue/login - 用户名 (可选):默认值为,用户可按需提供其他代码
apvhn - 密码 (可选):默认值为随机值,用户可按需提供特定密码
- 目标空间:要探索的空间的准确名称(例如:"HA GHT"、"STRUCTURES / NOMENCLATURES")
Critical Rules (learned from production runs)
关键规则(来自生产运行经验总结)
- Always use for any Vue.js interaction — NEVER use
page.mouse.click()viael.click(). Vue.js requires native mousedown/mouseup events that onlypage.evaluate()provides.page.mouse.click() - Re-expand sidebar before every page click — the sidebar collapses after each navigation. Using stale coordinates from initial discovery will miss the sidebar entirely.
- Re-discover item positions each iteration — sidebar coordinates shift on re-render. Always query the DOM for fresh coordinates.
- Use not
[class*="cursor:pointer"]for sidebar items — sidebar elements area.hexaelements, not<div>tags.<a> - Strip text from sidebar labels — icon text is prepended (e.g.,
<i class="hexa-icons">→tdbTableau de bord).Tableau de bord - Exclude utility buttons like "Trier par Importance" from the page list — they are sort controls in the Post-Its panel, not pages.
- Use narrow tab selectors (,
[role="tab"]) —.v-tabis too broad and picks up pagination, filters, and stale elements from previously rendered pages.[class*="tab"]
- 所有Vue.js交互必须使用—— 永远不要通过
page.mouse.click()调用page.evaluate()。Vue.js需要原生mousedown/mouseup事件,只有el.click()能提供。page.mouse.click() - 每次点击页面前都要重新展开侧边栏 —— 每次导航后边栏会自动收起,使用初始发现的旧坐标会完全点不到侧边栏。
- 每次迭代都要重新获取条目位置 —— 侧边栏重新渲染时坐标会发生变化,始终查询DOM获取最新坐标。
- 侧边栏条目使用而非
[class*="cursor:pointer"]选择器 —— 侧边栏元素是a.hexa元素,不是<div>标签。<a> - 剔除侧边栏标签中的文本 —— 图标文本会被前置(例如:
<i class="hexa-icons">→tdbTableau de bord)。Tableau de bord - 排除实用按钮 如页面列表中的"Trier par Importance" —— 它们是便签面板中的排序控件,不是页面。
- 使用范围更窄的标签页选择器(、
[role="tab"])——.v-tab范围过宽,会匹配分页、筛选器以及之前渲染页面的残留元素。[class*="tab"]
Troubleshooting
故障排查
| Problem | Cause | Solution |
|---|---|---|
| Space dropdown does not open | Used | Always use |
| All page screenshots are identical | Sidebar collapsed, clicks miss sidebar items | Re-expand sidebar + re-discover coordinates before each click |
| Sidebar items not found | Used | Use |
| Too many "tabs" detected | Broad selector | Use |
| "Trier par Importance" in page list | Sort button mistaken for page | Add to exclude list: |
| Fields not detected by Vue.js | Direct value injection without events | Use |
| Login button click selects wrong button | Multiple buttons on page | Use text-content matching: |
| Page content not loaded after click | Slow server or heavy page | Use |
| SSL certificate error | Self-signed cert on Hexagone Web server | |
| Malformed features.json | Check required fields: |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 空间下拉菜单无法打开 | 使用了 | Vue.js交互始终使用 |
| 所有页面截图都相同 | 侧边栏收起,点击未命中侧边栏条目 | 每次点击前重新展开侧边栏 + 重新获取坐标 |
| 找不到侧边栏条目 | 使用了 | 使用 |
| 检测到过多"标签页" | 使用了范围过宽的 | 仅使用 |
| 页面列表中出现"Trier par Importance" | 排序按钮被误认为是页面 | 添加到排除列表: |
| Vue.js未检测到字段值变更 | 直接注入值没有派发事件 | 使用 |
| 点击登录按钮选中了错误的按钮 | 页面上有多个按钮 | 使用文本内容匹配: |
| 点击后页面内容未加载 | 服务器响应慢或页面过重 | 使用 |
| SSL证书错误 | Hexagone Web服务器使用自签名证书 | 浏览器上下文配置 |
| | 检查必填字段: |