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) |
默认值针对标准Hexagone Web布局(1920x1080)校准。如果布局不同,请进行调整。
| 参数 | 默认值 | 描述 |
|---|---|---|
| 视口尺寸 | | 浏览器视口大小 |
| 侧边栏点击X坐标 | | 侧边栏图标点击的水平像素位置(折叠模式) |
| 侧边栏最大左边界 | | 识别侧边栏链接的最大 |
| 头部高度偏移 | | 排除头部元素的最小 |
| 登录等待超时 | | 轮询登录成功的最长时间 |
| 页面加载等待超时 | | 导航后轮询页面加载完成的最长时间 |
| 截图保存目录 | | 截图保存路径(相对于工作目录) |
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. 环境搭建 → 安装Playwright,启动无头Chromium
2. 连接系统 → 登录Hexagone Web
3. 导航定位 → 跳转至目标空间
4. 页面发现 → 展开侧边栏,列出所有菜单页面
5. 探索捕获 → 访问每个页面,捕获截图和元数据
6. 文档生成 → 生成嵌入截图的Markdown文档相较于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 // 自动处理自签名证书
});
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); // 等待Vue.js挂载完成2.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必须使用结合原生属性设置模式——这是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);关键注意事项:必须使用——不要通过调用。Vue.js事件处理程序需要原生鼠标事件(mousedown + mouseup + click),而JavaScript的仅触发事件,无法触发空间下拉菜单。这是开发过程中发现的头号问题。
page.mouse.click()page.evaluate()el.click()el.click()click空间选择器是橙色面包屑栏中带有类的元素,包含图标和显示当前空间名称的。
bg:orange-darkdiv<i class="hexa-icons">changer_espaces</i><span>javascript
// 获取空间选择器坐标
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;
});
// 使用原生鼠标事件点击(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
// 查找目标空间元素
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);
// 使用鼠标点击(不要用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); // 额外等待Vue.js渲染完成Step 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); // 汉堡图标位置
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();
// 移除图标前缀文本
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;
// 排除全大写的短文本(如"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--section">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—— Mes Post-Its面板中的排序按钮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.
如果发现后没有找到任何项:停止探索并截取调试截图,不要继续处理空页面列表,将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); // 切换汉堡菜单
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.
关键注意事项:点击菜单项后侧边栏会折叠,因此每次点击前必须重新展开侧边栏并重新获取项的位置。
遍历每个菜单项:
1. 展开侧边栏:page.mouse.click(34, 50),等待1.5秒
2. 通过page.evaluate()重新获取该项的位置(标签匹配)
3. 点击该项:page.mouse.click(freshPos.x, freshPos.y)
4. 等待页面加载(networkidle + 额外1.5秒等待)
5. 截取截图:page.screenshot({ path: ... })
6. 通过page.evaluate()提取元数据
7. 如果发现标签页,探索它们(见5.2)
8. 构建面向PO的功能描述为何每次都要重新获取位置? 侧边栏在重新展开时会重新渲染内容,项的坐标会根据滚动位置和可见项发生变化。使用初始发现的旧坐标会点击主内容区域而非侧边栏。
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部分页面包含内部标签页(例如:Fournisseurs → Saisie / Consultation / Réalisé,Marchés → SAISIE / CONSULTATION / DÉBLOCAGE)。标签页也需使用:
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 // 仅捕获视口内容(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": "探索的空间名称",
"features": [
{
"title": "功能名称(字符串,必填)",
"description": "面向PO的功能描述(字符串,必填)",
"capabilities": ["功能1", "功能2"],
"businessValue": "业务价值描述(字符串)",
"screenshots": [
{ "file": "filename.png", "caption": "截图描述" }
]
}
]
}验证规则:
- 必须为非空字符串
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--inputnpm install6.3 Document Structure
6.3 文档结构
undefinedundefinedTitle (space name)
标题(空间名称)
Subtitle with date
带日期的副标题
Table of contents (linked)
目录(带链接)
For each feature:
N. Feature title
- Functional description
- Screenshot(s) as
Key capabilities (numbered list)
Business value
undefined每个功能对应:
N. 功能标题
- 功能描述
- 截图:
核心功能(编号列表)
业务价值
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 # 结果应与功能数量一致Input 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(可选):默认值为,用户可根据需要提供其他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需要el.click()提供的原生mousedown/mouseup事件。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服务器使用自签名证书 | 在浏览器上下文设置 |
| | 检查必填字段: |