android-midscene-automation 0.1.11 → 0.1.13
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/server/appium-recorder/appium-runner.ts +32 -28
- package/server/appium-recorder/repository.ts +28 -11
- package/src/appium-recorder/AppiumPage.vue +44 -42
- package/src/appium-recorder/components/RecordedSteps.vue +27 -119
- package/src/appium-recorder/types.ts +0 -1
- package/src/style.css +5 -64
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## v0.1.13
|
|
4
|
+
|
|
5
|
+
- Appium 模块:修复加载历史脚本并新增节点后被误判为新脚本、无法覆盖保存的问题。
|
|
6
|
+
- Appium 模块:脚本末尾的连接节点改为当前脚本完成后的串联阶段,避免判断分支未命中时漏掉后一个脚本。
|
|
7
|
+
|
|
8
|
+
## v0.1.12
|
|
9
|
+
|
|
10
|
+
- 修复部分浏览器打开更新记录时中文乱码的问题,将 README 链接切换到明确返回 UTF-8 编码的 jsDelivr。
|
|
11
|
+
- Appium 模块:删除结束节点、前置操作和后置操作;“启动 APP”改为从开始节点直接插入为流程第一步,并对齐开始节点与插入按钮。
|
|
12
|
+
- Appium 模块:每个录制脚本只允许一个“启动 APP”节点;添加后操作菜单自动禁用该选项,删除节点后恢复。
|
|
13
|
+
- Appium 模块:连接脚本回放时自动跳过子脚本的“启动 APP”节点,子脚本独立回放时仍正常启动 App。
|
|
14
|
+
|
|
3
15
|
## v0.1.11
|
|
4
16
|
|
|
5
17
|
- 修复 npm 页面中的项目更新记录链接,改为使用可直接访问包文件的 unpkg 地址。
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -557,6 +557,10 @@ async function appendStepDiagnostics(lines: string[], deviceId: string, step: Ap
|
|
|
557
557
|
}
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
type ReplayStepOptions = {
|
|
561
|
+
skipLaunchApp?: boolean;
|
|
562
|
+
};
|
|
563
|
+
|
|
560
564
|
async function replayLinkedScript(
|
|
561
565
|
sessionId: string,
|
|
562
566
|
deviceId: string,
|
|
@@ -580,7 +584,9 @@ async function replayLinkedScript(
|
|
|
580
584
|
|
|
581
585
|
lines.push(`连接脚本开始:${linkedScript.name}`);
|
|
582
586
|
const nextStack = [...stack, linkedScript.id];
|
|
583
|
-
await replayScriptSteps(sessionId, deviceId, linkedScript.steps, lines, nextStack
|
|
587
|
+
await replayScriptSteps(sessionId, deviceId, linkedScript.steps, lines, nextStack, {
|
|
588
|
+
skipLaunchApp: true,
|
|
589
|
+
});
|
|
584
590
|
lines.push(`连接脚本完成:${linkedScript.name}`);
|
|
585
591
|
}
|
|
586
592
|
|
|
@@ -590,8 +596,13 @@ async function replayLinearSteps(
|
|
|
590
596
|
steps: AppiumRecordedStepRecord[],
|
|
591
597
|
lines: string[],
|
|
592
598
|
stack: string[] = [],
|
|
599
|
+
options: ReplayStepOptions = {},
|
|
593
600
|
) {
|
|
594
601
|
for (const [index, step] of steps.entries()) {
|
|
602
|
+
if (options.skipLaunchApp && step.type === 'launchApp') {
|
|
603
|
+
lines.push(`[步骤 ${index + 1}] 跳过:${step.label}(连接脚本不重复启动 App)`);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
595
606
|
lines.push(`[步骤 ${index + 1}] 开始:${step.label}`);
|
|
596
607
|
try {
|
|
597
608
|
if (step.type === 'runScript') {
|
|
@@ -619,6 +630,7 @@ async function replayFlowSteps(
|
|
|
619
630
|
steps: AppiumRecordedStepRecord[],
|
|
620
631
|
lines: string[],
|
|
621
632
|
stack: string[] = [],
|
|
633
|
+
options: ReplayStepOptions = {},
|
|
622
634
|
) {
|
|
623
635
|
const idToIndex = new Map(steps.map((step, index) => [step.id, index]));
|
|
624
636
|
const visitedPath: string[] = [];
|
|
@@ -631,6 +643,12 @@ async function replayFlowSteps(
|
|
|
631
643
|
if (guard > maxVisits) throw new Error('流程图可能存在循环,已终止回放');
|
|
632
644
|
|
|
633
645
|
const step = steps[index];
|
|
646
|
+
if (options.skipLaunchApp && step.type === 'launchApp') {
|
|
647
|
+
lines.push(`[节点 ${index + 1}] 跳过:${step.label}(连接脚本不重复启动 App)`);
|
|
648
|
+
const targetId = step.flow?.successTargetId;
|
|
649
|
+
index = targetId ? idToIndex.get(targetId) : index + 1;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
634
652
|
visitedPath.push(step.label);
|
|
635
653
|
const nodeKind = step.flow?.nodeKind || (step.type === 'assertExists' || step.type === 'assertText' ? 'assertion' : 'action');
|
|
636
654
|
lines.push(`[节点 ${index + 1}] 开始:${step.label}`);
|
|
@@ -679,12 +697,13 @@ async function replayStepGroup(
|
|
|
679
697
|
steps: AppiumRecordedStepRecord[],
|
|
680
698
|
lines: string[],
|
|
681
699
|
stack: string[],
|
|
700
|
+
options: ReplayStepOptions = {},
|
|
682
701
|
) {
|
|
683
702
|
if (!steps.length) return;
|
|
684
703
|
if (hasFlowSteps(steps)) {
|
|
685
|
-
await replayFlowSteps(sessionId, deviceId, steps, lines, stack);
|
|
704
|
+
await replayFlowSteps(sessionId, deviceId, steps, lines, stack, options);
|
|
686
705
|
} else {
|
|
687
|
-
await replayLinearSteps(sessionId, deviceId, steps, lines, stack);
|
|
706
|
+
await replayLinearSteps(sessionId, deviceId, steps, lines, stack, options);
|
|
688
707
|
}
|
|
689
708
|
}
|
|
690
709
|
|
|
@@ -694,36 +713,21 @@ async function replayScriptSteps(
|
|
|
694
713
|
steps: AppiumRecordedStepRecord[],
|
|
695
714
|
lines: string[],
|
|
696
715
|
stack: string[],
|
|
716
|
+
options: ReplayStepOptions = {},
|
|
697
717
|
) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
return;
|
|
718
|
+
let trailingLinkIndex = steps.length;
|
|
719
|
+
while (trailingLinkIndex > 0 && steps[trailingLinkIndex - 1]?.type === 'runScript') {
|
|
720
|
+
trailingLinkIndex -= 1;
|
|
702
721
|
}
|
|
703
722
|
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
let primaryError: unknown;
|
|
708
|
-
|
|
709
|
-
try {
|
|
710
|
-
if (preSteps.length) lines.push('执行前置操作...');
|
|
711
|
-
await replayStepGroup(sessionId, deviceId, preSteps, lines, stack);
|
|
712
|
-
if (mainSteps.length) lines.push('执行主流程...');
|
|
713
|
-
await replayStepGroup(sessionId, deviceId, mainSteps, lines, stack);
|
|
714
|
-
} catch (error) {
|
|
715
|
-
primaryError = error;
|
|
716
|
-
}
|
|
723
|
+
const mainSteps = steps.slice(0, trailingLinkIndex);
|
|
724
|
+
const trailingLinkedSteps = steps.slice(trailingLinkIndex);
|
|
725
|
+
await replayStepGroup(sessionId, deviceId, mainSteps, lines, stack, options);
|
|
717
726
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
await
|
|
721
|
-
} catch (error) {
|
|
722
|
-
if (!primaryError) throw error;
|
|
723
|
-
lines.push(`后置操作失败:${errorDetail(error)}`);
|
|
727
|
+
for (const step of trailingLinkedSteps) {
|
|
728
|
+
lines.push(`当前脚本步骤完成,开始执行:${step.label}`);
|
|
729
|
+
await replayLinkedScript(sessionId, deviceId, step, lines, stack);
|
|
724
730
|
}
|
|
725
|
-
|
|
726
|
-
if (primaryError) throw primaryError;
|
|
727
731
|
}
|
|
728
732
|
|
|
729
733
|
export async function replayAppiumScript(script: AppiumRecordedScriptRecord, deviceId: string) {
|
|
@@ -73,7 +73,6 @@ export type AppiumRecordedStepRecord = {
|
|
|
73
73
|
keyCode?: number;
|
|
74
74
|
timeoutMs?: number;
|
|
75
75
|
flow?: {
|
|
76
|
-
scope?: 'pre' | 'main' | 'post';
|
|
77
76
|
nodeKind?: 'action' | 'condition' | 'assertion';
|
|
78
77
|
yesTargetId?: string;
|
|
79
78
|
noTargetId?: string;
|
|
@@ -249,17 +248,35 @@ export function saveAppiumRecordedScript(input: {
|
|
|
249
248
|
if (!name) throw new Error('脚本名称不能为空');
|
|
250
249
|
if (!appPackage) throw new Error('App 包名不能为空');
|
|
251
250
|
|
|
252
|
-
if (input.id
|
|
251
|
+
if (input.id) {
|
|
253
252
|
runSql(`
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
name
|
|
257
|
-
app_package
|
|
258
|
-
app_activity
|
|
259
|
-
device_id
|
|
260
|
-
flow_json
|
|
261
|
-
|
|
262
|
-
|
|
253
|
+
INSERT INTO appium_recorded_scripts (
|
|
254
|
+
id,
|
|
255
|
+
name,
|
|
256
|
+
app_package,
|
|
257
|
+
app_activity,
|
|
258
|
+
device_id,
|
|
259
|
+
flow_json,
|
|
260
|
+
created_at,
|
|
261
|
+
updated_at
|
|
262
|
+
)
|
|
263
|
+
VALUES (
|
|
264
|
+
${sqlString(input.id)},
|
|
265
|
+
${sqlString(name)},
|
|
266
|
+
${sqlString(appPackage)},
|
|
267
|
+
${sqlNullableString(input.appActivity || '')},
|
|
268
|
+
${sqlNullableString(input.deviceId || '')},
|
|
269
|
+
${sqlJson(input.steps || [])},
|
|
270
|
+
${sqlString(now)},
|
|
271
|
+
${sqlString(now)}
|
|
272
|
+
)
|
|
273
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
274
|
+
name = excluded.name,
|
|
275
|
+
app_package = excluded.app_package,
|
|
276
|
+
app_activity = excluded.app_activity,
|
|
277
|
+
device_id = excluded.device_id,
|
|
278
|
+
flow_json = excluded.flow_json,
|
|
279
|
+
updated_at = excluded.updated_at;
|
|
263
280
|
`);
|
|
264
281
|
return getAppiumRecordedScript(input.id);
|
|
265
282
|
}
|
|
@@ -20,7 +20,7 @@ import type { AppiumNode, AppiumRecordedScript, AppiumRecordedStep } from './typ
|
|
|
20
20
|
|
|
21
21
|
type NodeStepType = 'tap' | 'input' | 'assertExists' | 'waitFor';
|
|
22
22
|
type BranchName = 'yes' | 'no';
|
|
23
|
-
type
|
|
23
|
+
type LegacyFlow = NonNullable<AppiumRecordedStep['flow']> & { scope?: string };
|
|
24
24
|
type BranchTarget = {
|
|
25
25
|
stepId: string;
|
|
26
26
|
branch: BranchName;
|
|
@@ -138,44 +138,47 @@ const selectedBounds = computed(() => {
|
|
|
138
138
|
return bounds && selectedNode.value ? { id: selectedNode.value.id, ...bounds } : undefined;
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
function stepScope(step?: AppiumRecordedStep): StepScope {
|
|
142
|
-
return step?.flow?.scope || 'main';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function insertionScope(index?: number): StepScope {
|
|
146
|
-
if (index === -2) return 'pre';
|
|
147
|
-
if (index === -3) return 'post';
|
|
148
|
-
return typeof index === 'number' && index >= 0 ? stepScope(steps.value[index]) : 'main';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function withStepScope(step: AppiumRecordedStep, scope: StepScope): AppiumRecordedStep {
|
|
152
|
-
if (scope === 'main') return step;
|
|
153
|
-
return {
|
|
154
|
-
...step,
|
|
155
|
-
flow: {
|
|
156
|
-
...(step.flow || {}),
|
|
157
|
-
scope,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
141
|
function insertStep(step: AppiumRecordedStep, index?: number) {
|
|
163
142
|
const nextSteps = [...steps.value];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
143
|
+
let insertedStep = step;
|
|
144
|
+
if (index === -1) {
|
|
145
|
+
nextSteps.unshift(step);
|
|
146
|
+
} else if (typeof index === 'number') {
|
|
147
|
+
const previousStep = nextSteps[index];
|
|
148
|
+
const previousTargetId = previousStep?.flow?.successTargetId;
|
|
149
|
+
if (previousStep && previousTargetId) {
|
|
150
|
+
insertedStep = {
|
|
151
|
+
...step,
|
|
152
|
+
flow: {
|
|
153
|
+
...(step.flow || {}),
|
|
154
|
+
successTargetId: previousTargetId,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
nextSteps[index] = {
|
|
158
|
+
...previousStep,
|
|
159
|
+
flow: {
|
|
160
|
+
...(previousStep.flow || {}),
|
|
161
|
+
successTargetId: insertedStep.id,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
nextSteps.splice(index + 1, 0, insertedStep);
|
|
173
166
|
} else {
|
|
174
|
-
|
|
175
|
-
nextSteps.splice(typeof index === 'number' ? index + 1 : nextSteps.length, 0, withStepScope(step, scope));
|
|
167
|
+
nextSteps.push(step);
|
|
176
168
|
}
|
|
177
169
|
steps.value = nextSteps;
|
|
178
|
-
return
|
|
170
|
+
return insertedStep;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function normalizeLegacyFlowScope(step: AppiumRecordedStep): AppiumRecordedStep {
|
|
174
|
+
const flow = step.flow as LegacyFlow | undefined;
|
|
175
|
+
if (!flow?.scope) return step;
|
|
176
|
+
const nextFlow = { ...flow };
|
|
177
|
+
delete nextFlow.scope;
|
|
178
|
+
return {
|
|
179
|
+
...step,
|
|
180
|
+
flow: Object.keys(nextFlow).length ? nextFlow : undefined,
|
|
181
|
+
};
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
function updateBranchTarget(stepId: string, branch: BranchName, targetId: string) {
|
|
@@ -632,13 +635,12 @@ async function addAction(
|
|
|
632
635
|
if (recordingBusy.value) return;
|
|
633
636
|
if (scriptActivityMismatch.value && !['keyBack', 'runScript', 'launchApp', 'delay'].includes(action)) return;
|
|
634
637
|
if (!ensureAppPackageSelected()) return;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
ElMessage.warning('前置和后置操作仅支持启动 APP 和添加延时');
|
|
638
|
+
if (action === 'launchApp' && steps.value.some((step) => step.type === 'launchApp')) {
|
|
639
|
+
ElMessage.warning('启动 APP 节点只能添加一个');
|
|
638
640
|
return;
|
|
639
641
|
}
|
|
640
|
-
if (
|
|
641
|
-
ElMessage.warning('启动 APP
|
|
642
|
+
if (action === 'launchApp' && index !== -1) {
|
|
643
|
+
ElMessage.warning('启动 APP 只能从开始节点添加');
|
|
642
644
|
return;
|
|
643
645
|
}
|
|
644
646
|
if (action === 'runScript') {
|
|
@@ -805,7 +807,7 @@ async function saveOnActivityChange() {
|
|
|
805
807
|
const scriptName = form.name.trim() || `${activityName}-${Date.now().toString(36).slice(-4)}`;
|
|
806
808
|
try {
|
|
807
809
|
const payload = await saveAppiumScript({
|
|
808
|
-
id:
|
|
810
|
+
id: selectedScriptId.value || undefined,
|
|
809
811
|
name: scriptName,
|
|
810
812
|
appPackage: form.appPackage,
|
|
811
813
|
appActivity: pending.beforeActivity || form.appActivity,
|
|
@@ -887,7 +889,7 @@ function loadScript(script: AppiumRecordedScript) {
|
|
|
887
889
|
if (script.deviceId && script.deviceId !== selectedDeviceId.value) {
|
|
888
890
|
void switchDevice(script.deviceId);
|
|
889
891
|
}
|
|
890
|
-
steps.value = script.steps || [];
|
|
892
|
+
steps.value = (script.steps || []).map(normalizeLegacyFlowScope);
|
|
891
893
|
activeWorkbenchTab.value = 'recording';
|
|
892
894
|
}
|
|
893
895
|
|
|
@@ -946,7 +948,7 @@ async function saveScript() {
|
|
|
946
948
|
saving.value = true;
|
|
947
949
|
try {
|
|
948
950
|
const payload = await saveAppiumScript({
|
|
949
|
-
id:
|
|
951
|
+
id: selectedScriptId.value || undefined,
|
|
950
952
|
name: form.name,
|
|
951
953
|
appPackage: form.appPackage,
|
|
952
954
|
appActivity: form.appActivity,
|
|
@@ -5,7 +5,6 @@ import type { AppiumRecordedStep, AppiumSelector } from '../types';
|
|
|
5
5
|
|
|
6
6
|
type FlowKind = 'action' | 'condition' | 'assertion';
|
|
7
7
|
type BranchName = 'yes' | 'no';
|
|
8
|
-
type StepScope = 'pre' | 'main' | 'post';
|
|
9
8
|
type StepItem = { step: AppiumRecordedStep; index: number };
|
|
10
9
|
type InsertAction =
|
|
11
10
|
| 'delay'
|
|
@@ -77,6 +76,8 @@ const stepItems = computed<StepItem[]>(() => props.steps
|
|
|
77
76
|
|
|
78
77
|
const visibleStepItems = computed(() => stepItems.value
|
|
79
78
|
.filter(({ step }) => !branchTargetIds.value.has(step.id)));
|
|
79
|
+
const hasFlowSteps = computed(() => visibleStepItems.value.length > 0);
|
|
80
|
+
const hasLaunchAppStep = computed(() => props.steps.some((step) => step.type === 'launchApp'));
|
|
80
81
|
|
|
81
82
|
const insertActionGroups: Array<{ title: string; actions: Array<{ type: InsertAction; label: string }> }> = [
|
|
82
83
|
{
|
|
@@ -130,28 +131,10 @@ const mainActionGroups = insertActionGroups.map((group) => ({
|
|
|
130
131
|
actions: group.actions.filter((action) => action.type !== 'launchApp'),
|
|
131
132
|
}));
|
|
132
133
|
|
|
133
|
-
const
|
|
134
|
-
title: '前后置操作',
|
|
135
|
-
actions: [
|
|
136
|
-
{ type: 'launchApp' as const, label: '启动 APP' },
|
|
137
|
-
{ type: 'delay' as const, label: '添加延时' },
|
|
138
|
-
],
|
|
139
|
-
}];
|
|
134
|
+
const startActionGroups = insertActionGroups;
|
|
140
135
|
|
|
141
|
-
function
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function scopedStepOptions(source: AppiumRecordedStep) {
|
|
146
|
-
const scope = stepScope(source);
|
|
147
|
-
return stepOptions.value.filter((option) => {
|
|
148
|
-
const target = props.steps.find((step) => step.id === option.value);
|
|
149
|
-
return target && stepScope(target) === scope;
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function actionGroupsForStep(step: AppiumRecordedStep) {
|
|
154
|
-
return stepScope(step) === 'main' ? mainActionGroups : boundaryActionGroups;
|
|
136
|
+
function scopedStepOptions() {
|
|
137
|
+
return stepOptions.value;
|
|
155
138
|
}
|
|
156
139
|
|
|
157
140
|
function stepMeta(step: AppiumRecordedStep) {
|
|
@@ -273,6 +256,10 @@ function insertAction(index: number, command: string | number | object) {
|
|
|
273
256
|
emit('insertAction', index, command as InsertAction);
|
|
274
257
|
}
|
|
275
258
|
|
|
259
|
+
function insertStartAction(command: string | number | object) {
|
|
260
|
+
emit('insertAction', -1, command as InsertAction);
|
|
261
|
+
}
|
|
262
|
+
|
|
276
263
|
function insertBranchAction(index: number, branch: BranchName, command: string | number | object) {
|
|
277
264
|
emit('insertBranchAction', index, branch, command as InsertAction);
|
|
278
265
|
}
|
|
@@ -281,6 +268,10 @@ function isInsertActionDisabled(action: InsertAction) {
|
|
|
281
268
|
return Boolean(props.disabled && !props.allowedLockedActions?.includes(action));
|
|
282
269
|
}
|
|
283
270
|
|
|
271
|
+
function isStartActionDisabled(action: InsertAction) {
|
|
272
|
+
return (action === 'launchApp' && hasLaunchAppStep.value) || isInsertActionDisabled(action);
|
|
273
|
+
}
|
|
274
|
+
|
|
284
275
|
function canOpenInsertMenu() {
|
|
285
276
|
return !props.disabled || Boolean(props.allowedLockedActions?.length);
|
|
286
277
|
}
|
|
@@ -381,48 +372,14 @@ watch(() => props.steps.length, () => {
|
|
|
381
372
|
class="appium-flow-content"
|
|
382
373
|
:style="{ transform: `translate(${flowPan.x}px, ${flowPan.y}px) scale(${flowScale})` }"
|
|
383
374
|
>
|
|
384
|
-
<div class="appium-flow-
|
|
385
|
-
<span class="appium-flow-boundary__label">前置操作</span>
|
|
386
|
-
<el-dropdown
|
|
387
|
-
trigger="click"
|
|
388
|
-
:disabled="!canOpenInsertMenu()"
|
|
389
|
-
max-height="320px"
|
|
390
|
-
popper-class="appium-action-dropdown"
|
|
391
|
-
@command="insertAction(-2, $event)"
|
|
392
|
-
>
|
|
393
|
-
<button
|
|
394
|
-
type="button"
|
|
395
|
-
class="appium-flow-insert"
|
|
396
|
-
:disabled="!canOpenInsertMenu()"
|
|
397
|
-
>
|
|
398
|
-
<el-icon><Timer /></el-icon>
|
|
399
|
-
<span>添加前置操作</span>
|
|
400
|
-
</button>
|
|
401
|
-
<template #dropdown>
|
|
402
|
-
<el-dropdown-menu>
|
|
403
|
-
<template v-for="group in boundaryActionGroups" :key="group.title">
|
|
404
|
-
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
405
|
-
<el-dropdown-item
|
|
406
|
-
v-for="action in group.actions"
|
|
407
|
-
:key="action.type"
|
|
408
|
-
:command="action.type"
|
|
409
|
-
:disabled="isInsertActionDisabled(action.type)"
|
|
410
|
-
>
|
|
411
|
-
{{ action.label }}
|
|
412
|
-
</el-dropdown-item>
|
|
413
|
-
</template>
|
|
414
|
-
</el-dropdown-menu>
|
|
415
|
-
</template>
|
|
416
|
-
</el-dropdown>
|
|
417
|
-
</div>
|
|
418
|
-
<div class="appium-flow-start">
|
|
375
|
+
<div class="appium-flow-start" :class="{ 'appium-flow-start--has-next': hasFlowSteps }">
|
|
419
376
|
<div class="appium-flow-start__node">开始</div>
|
|
420
377
|
<el-dropdown
|
|
421
378
|
trigger="click"
|
|
422
379
|
:disabled="!canOpenInsertMenu()"
|
|
423
380
|
max-height="320px"
|
|
424
381
|
popper-class="appium-action-dropdown"
|
|
425
|
-
@command="
|
|
382
|
+
@command="insertStartAction($event)"
|
|
426
383
|
>
|
|
427
384
|
<button
|
|
428
385
|
type="button"
|
|
@@ -434,13 +391,13 @@ watch(() => props.steps.length, () => {
|
|
|
434
391
|
</button>
|
|
435
392
|
<template #dropdown>
|
|
436
393
|
<el-dropdown-menu>
|
|
437
|
-
<template v-for="group in
|
|
394
|
+
<template v-for="group in startActionGroups" :key="group.title">
|
|
438
395
|
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
439
396
|
<el-dropdown-item
|
|
440
397
|
v-for="action in group.actions"
|
|
441
398
|
:key="action.type"
|
|
442
399
|
:command="action.type"
|
|
443
|
-
:disabled="
|
|
400
|
+
:disabled="isStartActionDisabled(action.type)"
|
|
444
401
|
>
|
|
445
402
|
{{ action.label }}
|
|
446
403
|
</el-dropdown-item>
|
|
@@ -453,10 +410,7 @@ watch(() => props.steps.length, () => {
|
|
|
453
410
|
v-for="{ step, index } in visibleStepItems"
|
|
454
411
|
:key="step.id"
|
|
455
412
|
class="appium-flow-item"
|
|
456
|
-
:class="
|
|
457
|
-
`appium-flow-item--${stepScope(step)}`,
|
|
458
|
-
{ 'appium-flow-item--condition': defaultKind(step) === 'condition' },
|
|
459
|
-
]"
|
|
413
|
+
:class="{ 'appium-flow-item--condition': defaultKind(step) === 'condition' }"
|
|
460
414
|
>
|
|
461
415
|
<div
|
|
462
416
|
class="appium-flow-node"
|
|
@@ -739,7 +693,7 @@ watch(() => props.steps.length, () => {
|
|
|
739
693
|
placeholder="默认下一步"
|
|
740
694
|
@update:model-value="patchFlow(index, { yesTargetId: String($event || '') })"
|
|
741
695
|
>
|
|
742
|
-
<el-option v-for="option in scopedStepOptions(
|
|
696
|
+
<el-option v-for="option in scopedStepOptions()" :key="option.value" :label="option.label" :value="option.value" />
|
|
743
697
|
</el-select>
|
|
744
698
|
</el-form-item>
|
|
745
699
|
<el-form-item label="否,进入">
|
|
@@ -750,7 +704,7 @@ watch(() => props.steps.length, () => {
|
|
|
750
704
|
placeholder="默认结束"
|
|
751
705
|
@update:model-value="patchFlow(index, { noTargetId: String($event || '') })"
|
|
752
706
|
>
|
|
753
|
-
<el-option v-for="option in scopedStepOptions(
|
|
707
|
+
<el-option v-for="option in scopedStepOptions()" :key="option.value" :label="option.label" :value="option.value" />
|
|
754
708
|
</el-select>
|
|
755
709
|
</el-form-item>
|
|
756
710
|
</div>
|
|
@@ -760,7 +714,7 @@ watch(() => props.steps.length, () => {
|
|
|
760
714
|
插入延时
|
|
761
715
|
</el-button>
|
|
762
716
|
<el-button
|
|
763
|
-
v-if="defaultKind(step) !== 'condition'
|
|
717
|
+
v-if="defaultKind(step) !== 'condition'"
|
|
764
718
|
:disabled="disabled"
|
|
765
719
|
@click="patchFlow(index, { nodeKind: 'condition', yesTargetId: '', noTargetId: '' })"
|
|
766
720
|
>
|
|
@@ -788,7 +742,7 @@ watch(() => props.steps.length, () => {
|
|
|
788
742
|
</button>
|
|
789
743
|
<template #dropdown>
|
|
790
744
|
<el-dropdown-menu>
|
|
791
|
-
<template v-for="group in
|
|
745
|
+
<template v-for="group in mainActionGroups" :key="group.title">
|
|
792
746
|
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
793
747
|
<el-dropdown-item
|
|
794
748
|
v-for="action in group.actions"
|
|
@@ -803,43 +757,6 @@ watch(() => props.steps.length, () => {
|
|
|
803
757
|
</template>
|
|
804
758
|
</el-dropdown>
|
|
805
759
|
</div>
|
|
806
|
-
<div class="appium-flow-end">
|
|
807
|
-
<div class="appium-flow-end__node">结束</div>
|
|
808
|
-
</div>
|
|
809
|
-
<div class="appium-flow-boundary appium-flow-boundary--post">
|
|
810
|
-
<span class="appium-flow-boundary__label">后置操作</span>
|
|
811
|
-
<el-dropdown
|
|
812
|
-
trigger="click"
|
|
813
|
-
:disabled="!canOpenInsertMenu()"
|
|
814
|
-
max-height="320px"
|
|
815
|
-
popper-class="appium-action-dropdown"
|
|
816
|
-
@command="insertAction(-3, $event)"
|
|
817
|
-
>
|
|
818
|
-
<button
|
|
819
|
-
type="button"
|
|
820
|
-
class="appium-flow-insert"
|
|
821
|
-
:disabled="!canOpenInsertMenu()"
|
|
822
|
-
>
|
|
823
|
-
<el-icon><Timer /></el-icon>
|
|
824
|
-
<span>添加后置操作</span>
|
|
825
|
-
</button>
|
|
826
|
-
<template #dropdown>
|
|
827
|
-
<el-dropdown-menu>
|
|
828
|
-
<template v-for="group in boundaryActionGroups" :key="group.title">
|
|
829
|
-
<div class="appium-action-dropdown__group">{{ group.title }}</div>
|
|
830
|
-
<el-dropdown-item
|
|
831
|
-
v-for="action in group.actions"
|
|
832
|
-
:key="action.type"
|
|
833
|
-
:command="action.type"
|
|
834
|
-
:disabled="isInsertActionDisabled(action.type)"
|
|
835
|
-
>
|
|
836
|
-
{{ action.label }}
|
|
837
|
-
</el-dropdown-item>
|
|
838
|
-
</template>
|
|
839
|
-
</el-dropdown-menu>
|
|
840
|
-
</template>
|
|
841
|
-
</el-dropdown>
|
|
842
|
-
</div>
|
|
843
760
|
</div>
|
|
844
761
|
</div>
|
|
845
762
|
<el-dialog
|
|
@@ -851,20 +768,17 @@ watch(() => props.steps.length, () => {
|
|
|
851
768
|
>
|
|
852
769
|
<div class="appium-flow-dialog-canvas">
|
|
853
770
|
<div class="appium-flow-content appium-flow-content--overview">
|
|
854
|
-
<div
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
771
|
+
<div
|
|
772
|
+
class="appium-flow-start appium-flow-start--overview"
|
|
773
|
+
:class="{ 'appium-flow-start--has-next': hasFlowSteps }"
|
|
774
|
+
>
|
|
858
775
|
<div class="appium-flow-start__node">开始</div>
|
|
859
776
|
</div>
|
|
860
777
|
<div
|
|
861
778
|
v-for="{ step, index } in visibleStepItems"
|
|
862
779
|
:key="step.id"
|
|
863
780
|
class="appium-flow-item"
|
|
864
|
-
:class="
|
|
865
|
-
`appium-flow-item--${stepScope(step)}`,
|
|
866
|
-
{ 'appium-flow-item--condition': defaultKind(step) === 'condition' },
|
|
867
|
-
]"
|
|
781
|
+
:class="{ 'appium-flow-item--condition': defaultKind(step) === 'condition' }"
|
|
868
782
|
>
|
|
869
783
|
<div
|
|
870
784
|
class="appium-flow-node"
|
|
@@ -928,12 +842,6 @@ watch(() => props.steps.length, () => {
|
|
|
928
842
|
</div>
|
|
929
843
|
</div>
|
|
930
844
|
</div>
|
|
931
|
-
<div class="appium-flow-end">
|
|
932
|
-
<div class="appium-flow-end__node">结束</div>
|
|
933
|
-
</div>
|
|
934
|
-
<div class="appium-flow-boundary appium-flow-boundary--post">
|
|
935
|
-
<span class="appium-flow-boundary__label">后置操作</span>
|
|
936
|
-
</div>
|
|
937
845
|
</div>
|
|
938
846
|
</div>
|
|
939
847
|
</el-dialog>
|
package/src/style.css
CHANGED
|
@@ -728,70 +728,7 @@ select {
|
|
|
728
728
|
gap: 6px;
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
-
.appium-flow-
|
|
732
|
-
position: relative;
|
|
733
|
-
z-index: 1;
|
|
734
|
-
display: flex;
|
|
735
|
-
align-items: center;
|
|
736
|
-
gap: 8px;
|
|
737
|
-
width: min(440px, 100%);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
.appium-flow-boundary__label {
|
|
741
|
-
min-width: 62px;
|
|
742
|
-
color: #606266;
|
|
743
|
-
font-size: 12px;
|
|
744
|
-
font-weight: 700;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
.appium-flow-boundary--pre {
|
|
748
|
-
order: 0;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
.appium-flow-item--pre {
|
|
752
|
-
order: 1;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
.appium-flow-start {
|
|
756
|
-
order: 2;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.appium-flow-item--main {
|
|
760
|
-
order: 3;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.appium-flow-end {
|
|
764
|
-
position: relative;
|
|
765
|
-
z-index: 1;
|
|
766
|
-
order: 4;
|
|
767
|
-
display: grid;
|
|
768
|
-
justify-items: start;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
.appium-flow-end__node {
|
|
772
|
-
display: grid;
|
|
773
|
-
place-items: center;
|
|
774
|
-
box-sizing: border-box;
|
|
775
|
-
width: 38px;
|
|
776
|
-
height: 30px;
|
|
777
|
-
padding: 0 6px;
|
|
778
|
-
border: 1px solid #909399;
|
|
779
|
-
border-radius: 8px;
|
|
780
|
-
background: #f4f4f5;
|
|
781
|
-
color: #606266;
|
|
782
|
-
font-size: 12px;
|
|
783
|
-
font-weight: 700;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
.appium-flow-boundary--post {
|
|
787
|
-
order: 5;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
.appium-flow-item--post {
|
|
791
|
-
order: 6;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
.appium-flow-start::after {
|
|
731
|
+
.appium-flow-start--has-next::after {
|
|
795
732
|
content: "";
|
|
796
733
|
position: absolute;
|
|
797
734
|
z-index: 0;
|
|
@@ -1198,6 +1135,10 @@ select {
|
|
|
1198
1135
|
opacity: 0.45;
|
|
1199
1136
|
}
|
|
1200
1137
|
|
|
1138
|
+
.appium-flow-start .appium-flow-insert {
|
|
1139
|
+
margin-left: 0;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1201
1142
|
.appium-linked-script-select {
|
|
1202
1143
|
width: 100%;
|
|
1203
1144
|
}
|