helixlife-v5-cli 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/helix-cli.js +16 -0
- package/lib/cli.js +223 -0
- package/package.json +34 -0
- package/references/vip.helixlife.cn.site-ops.md +1205 -0
- package/scripts/analysis-detail-upload-both.js +45 -0
- package/scripts/analysis-detail-upload-file.js +52 -0
- package/scripts/chrome-pdf-enumerate-a11y.js +16 -0
- package/scripts/chrome-pdf-goto-page.js +30 -0
- package/scripts/chrome-pdf-zoom-out.js +14 -0
- package/scripts/jigsaw-inspect-dom.js +15 -0
- package/scripts/jigsaw-verify-bio-tools.js +11 -0
- package/scripts/jigsaw-verify-drag-tool.js +23 -0
- package/scripts/jigsaw-verify-enumerate.js +54 -0
- package/scripts/jigsaw-verify-reference-lines.js +27 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
async page => {
|
|
2
|
+
const files = [
|
|
3
|
+
{
|
|
4
|
+
label: '数据矩阵',
|
|
5
|
+
path: 'C:/Users/zouyh/Desktop/WorkSpace/jieluoxuan/cli/helix-cli/.helixlife-v5-cli/差异分析-数据矩阵1.csv',
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
label: '样本信息',
|
|
9
|
+
path: 'C:/Users/zouyh/Desktop/WorkSpace/jieluoxuan/cli/helix-cli/.helixlife-v5-cli/差异分析-样本信息2.xlsx',
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const card = page
|
|
14
|
+
.locator('main .u-filter-card')
|
|
15
|
+
.filter({ hasText: '数据参数' });
|
|
16
|
+
|
|
17
|
+
for (const { label, path: filePath } of files) {
|
|
18
|
+
const row = card.getByText(label, { exact: true }).locator('..');
|
|
19
|
+
const openUpload = row
|
|
20
|
+
.getByLabel('上传文件 - 打开上传弹窗')
|
|
21
|
+
.or(row.getByLabel('上传文件'));
|
|
22
|
+
await openUpload.click();
|
|
23
|
+
|
|
24
|
+
const dlg = page.getByRole('dialog', { name: '上传文件' });
|
|
25
|
+
await dlg.waitFor({ state: 'visible', timeout: 8000 });
|
|
26
|
+
|
|
27
|
+
await dlg.locator('input[type="file"]').setInputFiles(filePath);
|
|
28
|
+
await page.waitForTimeout(500);
|
|
29
|
+
await dlg.getByRole('button', { name: '确认' }).click();
|
|
30
|
+
await dlg.waitFor({ state: 'hidden', timeout: 15000 });
|
|
31
|
+
await page.waitForTimeout(1500);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if ((await card.getByRole('button', { name: '验证' }).count()) > 0) {
|
|
35
|
+
await card.getByRole('button', { name: '验证' }).click();
|
|
36
|
+
await page.waitForTimeout(2000);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const text = await card.innerText();
|
|
40
|
+
return {
|
|
41
|
+
files: files.map(f => ({ label: f.label, file: f.path.split(/[/\\]/).pop() })),
|
|
42
|
+
verifySuccess: text.includes('验证成功'),
|
|
43
|
+
verifyFail: text.includes('验证失败'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据分析 · 工具详情页 · 上传文件(D-2 三步弹窗)
|
|
3
|
+
*
|
|
4
|
+
* 1) getByLabel('上传文件 - 打开上传弹窗') → dialog "上传文件"
|
|
5
|
+
* 2) 弹窗内 button /将文件拖到此处|点击上传/ + filechooser
|
|
6
|
+
* 3) 弹窗内 button「确认」
|
|
7
|
+
*
|
|
8
|
+
* 修改 filePath / paramLabel 后执行:
|
|
9
|
+
* helixlife-v5-cli run-code --filename=scripts/analysis-detail-upload-file.js
|
|
10
|
+
*/
|
|
11
|
+
async page => {
|
|
12
|
+
const filePath = 'C:/Users/zouyh/Desktop/helix-test-upload.csv';
|
|
13
|
+
const paramLabel = null; // 如 '数据矩阵';null 表示单上传行或取第一行
|
|
14
|
+
|
|
15
|
+
const card = page.locator('main .u-filter-card');
|
|
16
|
+
const row = paramLabel
|
|
17
|
+
? card.getByText(paramLabel, { exact: true }).locator('..')
|
|
18
|
+
: card;
|
|
19
|
+
const openUpload = (paramLabel ? row : card)
|
|
20
|
+
.getByLabel('上传文件 - 打开上传弹窗')
|
|
21
|
+
.or((paramLabel ? row : card).getByLabel('上传文件'))
|
|
22
|
+
.first();
|
|
23
|
+
await openUpload.click();
|
|
24
|
+
|
|
25
|
+
const dlg = page.getByRole('dialog', { name: '上传文件' });
|
|
26
|
+
await dlg.waitFor({ state: 'visible', timeout: 8000 });
|
|
27
|
+
|
|
28
|
+
const uploadBtn = dlg.getByRole('button', {
|
|
29
|
+
name: /将文件拖到此处|点击上传/,
|
|
30
|
+
});
|
|
31
|
+
const [chooser] = await Promise.all([
|
|
32
|
+
page.waitForEvent('filechooser', { timeout: 8000 }),
|
|
33
|
+
uploadBtn.click(),
|
|
34
|
+
]);
|
|
35
|
+
await chooser.setFiles(filePath);
|
|
36
|
+
await dlg.getByRole('button', { name: '确认' }).click();
|
|
37
|
+
await dlg.waitFor({ state: 'hidden', timeout: 8000 }).catch(() => {});
|
|
38
|
+
|
|
39
|
+
const main = page.locator('main');
|
|
40
|
+
await page.waitForTimeout(1500);
|
|
41
|
+
if ((await main.getByRole('button', { name: '验证' }).count()) > 0) {
|
|
42
|
+
await main.getByRole('button', { name: '验证' }).click();
|
|
43
|
+
await page.waitForTimeout(2000);
|
|
44
|
+
}
|
|
45
|
+
const text = await main.innerText();
|
|
46
|
+
return {
|
|
47
|
+
filePath,
|
|
48
|
+
paramLabel,
|
|
49
|
+
verifySuccess: text.includes('验证成功'),
|
|
50
|
+
verifyFail: text.includes('验证失败'),
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 枚举当前 PDF 阅读器帧内带 aria-label 的节点(排障 / 对照 site-ops 控件表)。
|
|
3
|
+
* 运行:helixlife-v5-cli run-code --filename=scripts/chrome-pdf-enumerate-a11y.js
|
|
4
|
+
*/
|
|
5
|
+
async page => {
|
|
6
|
+
const PDF_VIEWER_PREFIX = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai';
|
|
7
|
+
const fr = page.frames().find(f => f.url().startsWith(PDF_VIEWER_PREFIX));
|
|
8
|
+
if (!fr)
|
|
9
|
+
throw new Error(
|
|
10
|
+
`未找到 Chrome 内置 PDF 阅读器 frame(前缀 ${PDF_VIEWER_PREFIX})。请确认「课件」Tab 与 PDF 已加载。`
|
|
11
|
+
);
|
|
12
|
+
const labels = await fr.locator('[aria-label]').evaluateAll(elements =>
|
|
13
|
+
[...new Set(elements.map(el => el.getAttribute('aria-label')).filter(Boolean))].sort()
|
|
14
|
+
);
|
|
15
|
+
return { count: labels.length, ariaLabels: labels };
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helix 学习中心 · 课件 Tab · Chrome 内置 PDF 页码跳转(S-25)。
|
|
3
|
+
* 执行前将 target 改为用户口述的第 N 页(1 起计)。
|
|
4
|
+
* 运行:helixlife-v5-cli run-code --filename=scripts/chrome-pdf-goto-page.js
|
|
5
|
+
*/
|
|
6
|
+
async page => {
|
|
7
|
+
const PDF_VIEWER_PREFIX = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai';
|
|
8
|
+
const target = 1;
|
|
9
|
+
|
|
10
|
+
const fr = page.frames().find(f => f.url().startsWith(PDF_VIEWER_PREFIX));
|
|
11
|
+
if (!fr)
|
|
12
|
+
throw new Error(
|
|
13
|
+
`未找到 Chrome 内置 PDF 阅读器 frame(期望 url 前缀 ${PDF_VIEWER_PREFIX})。请确认已在「课件」Tab 且 PDF 已加载。`
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const pageInput = fr.getByLabel('页码');
|
|
17
|
+
const want = String(target);
|
|
18
|
+
|
|
19
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
20
|
+
await pageInput.click({ clickCount: 3 });
|
|
21
|
+
await pageInput.fill(want);
|
|
22
|
+
await pageInput.press('Enter');
|
|
23
|
+
const v = await pageInput.inputValue();
|
|
24
|
+
if (v === want)
|
|
25
|
+
return { ok: true, target, inputValue: v, attempts: attempt };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const last = await pageInput.inputValue();
|
|
29
|
+
return { ok: false, target, inputValue: last, attempts: 2 };
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome 内置 PDF 工具栏 · 点击「缩小」。
|
|
3
|
+
* 运行:helixlife-v5-cli run-code --filename=scripts/chrome-pdf-zoom-out.js
|
|
4
|
+
*/
|
|
5
|
+
async page => {
|
|
6
|
+
const PDF_VIEWER_PREFIX = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai';
|
|
7
|
+
const fr = page.frames().find(f => f.url().startsWith(PDF_VIEWER_PREFIX));
|
|
8
|
+
if (!fr)
|
|
9
|
+
throw new Error(
|
|
10
|
+
`未找到 Chrome 内置 PDF 阅读器 frame(前缀 ${PDF_VIEWER_PREFIX})。请确认「课件」Tab 与 PDF 已加载。`
|
|
11
|
+
);
|
|
12
|
+
await fr.getByRole('button', { name: '缩小' }).click();
|
|
13
|
+
return { ok: true };
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async page => {
|
|
2
|
+
return page.evaluate(() => {
|
|
3
|
+
const root = document.querySelector('.jigsaw');
|
|
4
|
+
if (!root) return null;
|
|
5
|
+
const walk = (el, depth = 0) => {
|
|
6
|
+
if (depth > 4) return null;
|
|
7
|
+
return {
|
|
8
|
+
tag: el.tagName,
|
|
9
|
+
cls: (el.className || '').toString().slice(0, 80),
|
|
10
|
+
ch: [...el.children].slice(0, 8).map(c => walk(c, depth + 1)),
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
return [...root.children].map(c => walk(c, 0));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 展开生信工具 Ant Select 并枚举选项 */
|
|
2
|
+
async page => {
|
|
3
|
+
const main = page.locator('main');
|
|
4
|
+
const selectWrap = main.locator('generic[title="生信工具"]').locator('..');
|
|
5
|
+
const combo = selectWrap.getByRole('combobox');
|
|
6
|
+
await combo.click({ force: true });
|
|
7
|
+
await page.waitForTimeout(600);
|
|
8
|
+
const options = await page.locator('.ant-select-item-option-content').allTextContents();
|
|
9
|
+
await page.keyboard.press('Escape');
|
|
10
|
+
return { optionCount: options.length, options: options.slice(0, 40) };
|
|
11
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** 自左侧 .jigsaw-images 拖入 .analysis-right-box */
|
|
2
|
+
async page => {
|
|
3
|
+
const main = page.locator('main');
|
|
4
|
+
const tiff = main.getByRole('button', { name: 'TIFF下载' });
|
|
5
|
+
const beforeDisabled = await tiff.isDisabled();
|
|
6
|
+
|
|
7
|
+
const src = page.locator('.jigsaw-images .cursor-move').first();
|
|
8
|
+
const board = page.locator('.analysis-right-box');
|
|
9
|
+
await src.dragTo(board, { targetPosition: { x: 120, y: 120 } });
|
|
10
|
+
await page.waitForTimeout(1500);
|
|
11
|
+
|
|
12
|
+
const placed = await board.evaluate(box => ({
|
|
13
|
+
childCount: box.children.length,
|
|
14
|
+
htmlLen: box.innerHTML.length,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
beforeDisabled,
|
|
19
|
+
afterDisabled: await tiff.isDisabled(),
|
|
20
|
+
pdfDisabled: await main.getByRole('button', { name: 'PDF下载' }).isDisabled(),
|
|
21
|
+
placed,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** 拼图工具页 · 非破坏性枚举。helixlife-v5-cli run-code --filename=scripts/jigsaw-verify-enumerate.js */
|
|
2
|
+
async page => {
|
|
3
|
+
const main = page.locator('main');
|
|
4
|
+
const out = {
|
|
5
|
+
pathname: await page.evaluate(() => location.pathname),
|
|
6
|
+
title: await page.title(),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const abcBtn = main.getByRole('button', { name: 'ABC标注' });
|
|
10
|
+
if ((await abcBtn.getAttribute('aria-expanded')) !== 'true') {
|
|
11
|
+
await abcBtn.click();
|
|
12
|
+
await page.waitForTimeout(400);
|
|
13
|
+
}
|
|
14
|
+
out.abcExpanded = (await abcBtn.getAttribute('aria-expanded')) === 'true';
|
|
15
|
+
|
|
16
|
+
const labels = [
|
|
17
|
+
'是否标注',
|
|
18
|
+
'字体',
|
|
19
|
+
'标注',
|
|
20
|
+
'标注大小',
|
|
21
|
+
'左偏斜',
|
|
22
|
+
'上偏斜',
|
|
23
|
+
'固定输出图片宽度',
|
|
24
|
+
'固定输出图片高度',
|
|
25
|
+
];
|
|
26
|
+
out.abcFieldsVisible = {};
|
|
27
|
+
for (const label of labels) {
|
|
28
|
+
out.abcFieldsVisible[label] = await main.getByText(label, { exact: true }).first().isVisible();
|
|
29
|
+
}
|
|
30
|
+
out.annotationSwitchChecked = await main
|
|
31
|
+
.getByText('是否标注', { exact: true })
|
|
32
|
+
.locator('..')
|
|
33
|
+
.locator('[role=switch]')
|
|
34
|
+
.first()
|
|
35
|
+
.getAttribute('aria-checked');
|
|
36
|
+
out.annotationText = await main.getByRole('textbox', { name: '逗号间隔' }).inputValue();
|
|
37
|
+
out.fontShown = await main.locator('text=Arial').first().isVisible().catch(() => false);
|
|
38
|
+
out.sizeShown = await main.locator('text=15pt').first().isVisible().catch(() => false);
|
|
39
|
+
|
|
40
|
+
out.comboboxCount = await main.getByRole('combobox').count();
|
|
41
|
+
out.bioLabelVisible = await main.getByText('生信工具', { exact: true }).isVisible();
|
|
42
|
+
|
|
43
|
+
out.referenceLineButtons = {
|
|
44
|
+
horizontal: await main.getByText('横向参考线', { exact: true }).isVisible(),
|
|
45
|
+
vertical: await main.getByText('竖向参考线', { exact: true }).isVisible(),
|
|
46
|
+
clear: await main.getByText('清除参考线', { exact: true }).isVisible(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
out.tiffDisabled = await main.getByRole('button', { name: 'TIFF下载' }).isDisabled();
|
|
50
|
+
out.pdfDisabled = await main.getByRole('button', { name: 'PDF下载' }).isDisabled();
|
|
51
|
+
out.historyVisible = await main.getByText('历史记录', { exact: true }).isVisible();
|
|
52
|
+
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** 参考线:横向 → 竖向 → 清除;用页面内标尺区 DOM 变化作辅验收 */
|
|
2
|
+
async page => {
|
|
3
|
+
const main = page.locator('main');
|
|
4
|
+
const countGuides = () =>
|
|
5
|
+
page.evaluate(() => {
|
|
6
|
+
const mainEl = document.querySelector('main');
|
|
7
|
+
if (!mainEl) return 0;
|
|
8
|
+
const texts = [...mainEl.querySelectorAll('*')].filter(
|
|
9
|
+
el => el.childElementCount === 0 && /^\d+$/.test((el.textContent || '').trim())
|
|
10
|
+
);
|
|
11
|
+
const svgs = mainEl.querySelectorAll('svg line, svg path[stroke]');
|
|
12
|
+
return { svgLines: svgs.length, rulerNums: texts.length };
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const before = await countGuides();
|
|
16
|
+
await main.getByText('横向参考线', { exact: true }).click();
|
|
17
|
+
await page.waitForTimeout(400);
|
|
18
|
+
const afterH = await countGuides();
|
|
19
|
+
await main.getByText('竖向参考线', { exact: true }).click();
|
|
20
|
+
await page.waitForTimeout(400);
|
|
21
|
+
const afterV = await countGuides();
|
|
22
|
+
await main.getByText('清除参考线', { exact: true }).click();
|
|
23
|
+
await page.waitForTimeout(400);
|
|
24
|
+
const afterClear = await countGuides();
|
|
25
|
+
|
|
26
|
+
return { before, afterH, afterV, afterClear, clicksOk: true };
|
|
27
|
+
}
|