android-midscene-automation 0.1.13 → 0.1.14
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 +30 -0
- package/README.md +39 -51
- package/USAGE.md +1018 -0
- package/package.json +2 -1
- package/server/android-sdk.ts +104 -0
- package/server/appium-recorder/appium-runner.ts +144 -35
- package/server/appium-recorder/report.ts +138 -0
- package/server/appium-recorder/repository.ts +78 -0
- package/server/appium-recorder/routes.ts +141 -14
- package/server/config.ts +12 -0
- package/server/http-api.ts +9 -6
- package/src/App.vue +23 -23
- package/src/appium-recorder/AppiumPage.vue +403 -38
- package/src/appium-recorder/api.ts +62 -5
- package/src/appium-recorder/components/FlowStepEditor.vue +175 -0
- package/src/appium-recorder/components/RecordedSteps.vue +514 -277
- package/src/appium-recorder/types.ts +2 -0
- package/src/pages/ConfigPage.vue +28 -0
- package/src/style.css +162 -89
- package/src/types.ts +4 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, reactive, shallowRef, watch } from 'vue';
|
|
3
|
-
import { Delete, Edit, FullScreen, Timer } from '@element-plus/icons-vue';
|
|
4
|
-
import type { AppiumRecordedStep
|
|
3
|
+
import { Delete, Edit, FullScreen, Link, Timer, VideoPlay } from '@element-plus/icons-vue';
|
|
4
|
+
import type { AppiumRecordedStep } from '../types';
|
|
5
|
+
import FlowStepEditor from './FlowStepEditor.vue';
|
|
5
6
|
|
|
6
7
|
type FlowKind = 'action' | 'condition' | 'assertion';
|
|
7
8
|
type BranchName = 'yes' | 'no';
|
|
9
|
+
type FlowViewport = 'main' | 'overview';
|
|
8
10
|
type StepItem = { step: AppiumRecordedStep; index: number };
|
|
9
11
|
type InsertAction =
|
|
10
12
|
| 'delay'
|
|
@@ -36,6 +38,7 @@ const props = defineProps<{
|
|
|
36
38
|
steps: AppiumRecordedStep[];
|
|
37
39
|
disabled?: boolean;
|
|
38
40
|
allowedLockedActions?: InsertAction[];
|
|
41
|
+
launchingStepId?: string;
|
|
39
42
|
}>();
|
|
40
43
|
|
|
41
44
|
const emit = defineEmits<{
|
|
@@ -43,7 +46,10 @@ const emit = defineEmits<{
|
|
|
43
46
|
addDelay: [index?: number];
|
|
44
47
|
insertAction: [index: number, action: InsertAction];
|
|
45
48
|
insertBranchAction: [index: number, branch: BranchName, action: InsertAction];
|
|
49
|
+
connectNext: [index: number, branch: BranchName];
|
|
50
|
+
disconnectNext: [index: number, branch: BranchName];
|
|
46
51
|
editInput: [index: number];
|
|
52
|
+
executeStep: [index: number];
|
|
47
53
|
updateStep: [index: number, step: AppiumRecordedStep];
|
|
48
54
|
}>();
|
|
49
55
|
|
|
@@ -51,21 +57,16 @@ const expandedStepIndex = shallowRef<number | null>(null);
|
|
|
51
57
|
const flowDialogVisible = shallowRef(false);
|
|
52
58
|
const flowPan = reactive({ x: 0, y: 0 });
|
|
53
59
|
const flowScale = shallowRef(1);
|
|
54
|
-
const
|
|
60
|
+
const overviewPan = reactive({ x: 0, y: 0 });
|
|
61
|
+
const overviewScale = shallowRef(1);
|
|
62
|
+
const draggingViewport = shallowRef<FlowViewport | null>(null);
|
|
55
63
|
const flowDragMoved = shallowRef(false);
|
|
56
|
-
let dragStart = { pointerId: 0, x: 0, y: 0, panX: 0, panY: 0 };
|
|
57
|
-
|
|
58
|
-
const stepOptions = computed(() => props.steps.map((step, index) => ({
|
|
59
|
-
label: `${index + 1}. ${step.label}`,
|
|
60
|
-
value: step.id,
|
|
61
|
-
})));
|
|
64
|
+
let dragStart = { pointerId: 0, x: 0, y: 0, panX: 0, panY: 0, viewport: 'main' as FlowViewport };
|
|
62
65
|
|
|
63
66
|
const branchTargetIds = computed(() => {
|
|
64
67
|
const ids = new Set<string>();
|
|
65
68
|
props.steps.forEach((step) => {
|
|
66
69
|
if (step.flow?.parentConditionId) ids.add(step.id);
|
|
67
|
-
if (step.flow?.yesTargetId) ids.add(step.flow.yesTargetId);
|
|
68
|
-
if (step.flow?.noTargetId) ids.add(step.flow.noTargetId);
|
|
69
70
|
});
|
|
70
71
|
return ids;
|
|
71
72
|
});
|
|
@@ -78,6 +79,23 @@ const visibleStepItems = computed(() => stepItems.value
|
|
|
78
79
|
.filter(({ step }) => !branchTargetIds.value.has(step.id)));
|
|
79
80
|
const hasFlowSteps = computed(() => visibleStepItems.value.length > 0);
|
|
80
81
|
const hasLaunchAppStep = computed(() => props.steps.some((step) => step.type === 'launchApp'));
|
|
82
|
+
const flowAxisByStepId = computed(() => {
|
|
83
|
+
const axes = new Map<string, number>();
|
|
84
|
+
let inheritedAxis = 50;
|
|
85
|
+
visibleStepItems.value.forEach(({ step }) => {
|
|
86
|
+
const incomingAxes: number[] = [];
|
|
87
|
+
visibleStepItems.value.forEach(({ step: condition }) => {
|
|
88
|
+
if (defaultKind(condition) !== 'condition') return;
|
|
89
|
+
if (branchConnectionTargetId(condition, 'yes') === step.id) incomingAxes.push(25);
|
|
90
|
+
if (branchConnectionTargetId(condition, 'no') === step.id) incomingAxes.push(75);
|
|
91
|
+
});
|
|
92
|
+
if (incomingAxes.length) {
|
|
93
|
+
inheritedAxis = incomingAxes.reduce((total, axis) => total + axis, 0) / incomingAxes.length;
|
|
94
|
+
}
|
|
95
|
+
axes.set(step.id, inheritedAxis);
|
|
96
|
+
});
|
|
97
|
+
return axes;
|
|
98
|
+
});
|
|
81
99
|
|
|
82
100
|
const insertActionGroups: Array<{ title: string; actions: Array<{ type: InsertAction; label: string }> }> = [
|
|
83
101
|
{
|
|
@@ -133,13 +151,12 @@ const mainActionGroups = insertActionGroups.map((group) => ({
|
|
|
133
151
|
|
|
134
152
|
const startActionGroups = insertActionGroups;
|
|
135
153
|
|
|
136
|
-
function scopedStepOptions() {
|
|
137
|
-
return stepOptions.value;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
154
|
function stepMeta(step: AppiumRecordedStep) {
|
|
141
155
|
if (step.type === 'delay') return `${step.timeoutMs || 1000}ms`;
|
|
142
156
|
if (step.type === 'input' || step.type === 'inputIfExists') return `输入内容:${step.value || '空'}`;
|
|
157
|
+
if (defaultKind(step) === 'condition' && step.value) {
|
|
158
|
+
return `${step.flow?.textMatch === 'exact' ? '精准匹配' : '模糊匹配'}:${step.value}`;
|
|
159
|
+
}
|
|
143
160
|
if (step.contextSelector && step.selector) {
|
|
144
161
|
return `父级 ${step.contextSelector.strategy} ${step.contextSelector.value || ''} + 子级 ${step.selector.strategy} ${step.selector.value || ''}`;
|
|
145
162
|
}
|
|
@@ -207,49 +224,80 @@ function targetLabel(id?: string) {
|
|
|
207
224
|
return `${index + 1}. ${props.steps[index].label}`;
|
|
208
225
|
}
|
|
209
226
|
|
|
210
|
-
function targetStep(id?: string) {
|
|
211
|
-
if (!id) return null;
|
|
212
|
-
const index = props.steps.findIndex((step) => step.id === id);
|
|
213
|
-
if (index < 0) return null;
|
|
214
|
-
return { step: props.steps[index], index };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
227
|
function branchStepItems(step: AppiumRecordedStep, branch: BranchName) {
|
|
218
|
-
|
|
228
|
+
return stepItems.value.filter((item) => (
|
|
219
229
|
item.step.flow?.parentConditionId === step.id
|
|
220
230
|
&& item.step.flow?.parentBranch === branch
|
|
221
231
|
));
|
|
222
|
-
|
|
232
|
+
}
|
|
223
233
|
|
|
234
|
+
function branchConnectionTargetId(step: AppiumRecordedStep, branch: BranchName) {
|
|
235
|
+
const branchItems = branchStepItems(step, branch);
|
|
236
|
+
if (branchItems.length) {
|
|
237
|
+
return branchItems[branchItems.length - 1]?.step.flow?.successTargetId || '';
|
|
238
|
+
}
|
|
224
239
|
const targetId = branch === 'yes' ? step.flow?.yesTargetId : step.flow?.noTargetId;
|
|
225
|
-
const target =
|
|
226
|
-
return target ?
|
|
240
|
+
const target = props.steps.find((item) => item.id === targetId);
|
|
241
|
+
return target && !target.flow?.parentConditionId ? target.id : '';
|
|
227
242
|
}
|
|
228
243
|
|
|
229
|
-
function
|
|
230
|
-
return
|
|
244
|
+
function hasBranchConnection(step: AppiumRecordedStep) {
|
|
245
|
+
return Boolean(
|
|
246
|
+
branchConnectionTargetId(step, 'yes')
|
|
247
|
+
|| branchConnectionTargetId(step, 'no'),
|
|
248
|
+
);
|
|
231
249
|
}
|
|
232
250
|
|
|
233
|
-
function
|
|
234
|
-
|
|
251
|
+
function hasFollowingMainStep(index: number) {
|
|
252
|
+
return props.steps.slice(index + 1).some((step) => !step.flow?.parentConditionId);
|
|
235
253
|
}
|
|
236
254
|
|
|
237
|
-
function
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
emit('updateStep', index, { ...current, ...patch });
|
|
255
|
+
function hasFollowingVisibleStep(step: AppiumRecordedStep) {
|
|
256
|
+
const index = visibleStepItems.value.findIndex((item) => item.step.id === step.id);
|
|
257
|
+
return index >= 0 && index < visibleStepItems.value.length - 1;
|
|
241
258
|
}
|
|
242
259
|
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
260
|
+
function shouldShowBranchConnect(index: number, step: AppiumRecordedStep, branch: BranchName) {
|
|
261
|
+
return Boolean(branchConnectionTargetId(step, branch)) || hasFollowingMainStep(index);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function flowAxis(step: AppiumRecordedStep) {
|
|
265
|
+
return flowAxisByStepId.value.get(step.id) || 50;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function flowAxisStyle(step: AppiumRecordedStep) {
|
|
269
|
+
return { '--flow-axis': `${flowAxis(step)}%` };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function branchPath(step: AppiumRecordedStep, branch: BranchName) {
|
|
273
|
+
const targetAxis = branch === 'yes' ? 25 : 75;
|
|
274
|
+
return `M${flowAxis(step)} 0 C${flowAxis(step)} 16 ${targetAxis} 16 ${targetAxis} 31 L${targetAxis} 48`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function mergeTargetAxis(step: AppiumRecordedStep) {
|
|
278
|
+
const axes = [
|
|
279
|
+
branchConnectionTargetId(step, 'yes') ? 25 : 0,
|
|
280
|
+
branchConnectionTargetId(step, 'no') ? 75 : 0,
|
|
281
|
+
].filter(Boolean);
|
|
282
|
+
return axes.reduce((total, axis) => total + axis, 0) / axes.length;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function mergePath(step: AppiumRecordedStep, branch: BranchName) {
|
|
286
|
+
const sourceAxis = branch === 'yes' ? 25 : 75;
|
|
287
|
+
const targetAxis = mergeTargetAxis(step);
|
|
288
|
+
return `M${sourceAxis} 0 C${sourceAxis} 20 ${targetAxis} 20 ${targetAxis} 44`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function toggleBranchConnection(index: number, step: AppiumRecordedStep, branch: BranchName) {
|
|
292
|
+
if (branchConnectionTargetId(step, branch)) {
|
|
293
|
+
emit('disconnectNext', index, branch);
|
|
294
|
+
} else {
|
|
295
|
+
emit('connectNext', index, branch);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function toggleExpanded(index: number) {
|
|
300
|
+
expandedStepIndex.value = expandedStepIndex.value === index ? null : index;
|
|
253
301
|
}
|
|
254
302
|
|
|
255
303
|
function insertAction(index: number, command: string | number | object) {
|
|
@@ -264,6 +312,10 @@ function insertBranchAction(index: number, branch: BranchName, command: string |
|
|
|
264
312
|
emit('insertBranchAction', index, branch, command as InsertAction);
|
|
265
313
|
}
|
|
266
314
|
|
|
315
|
+
function updateStep(payload: { index: number; step: AppiumRecordedStep }) {
|
|
316
|
+
emit('updateStep', payload.index, payload.step);
|
|
317
|
+
}
|
|
318
|
+
|
|
267
319
|
function isInsertActionDisabled(action: InsertAction) {
|
|
268
320
|
return Boolean(props.disabled && !props.allowedLockedActions?.includes(action));
|
|
269
321
|
}
|
|
@@ -272,13 +324,19 @@ function isStartActionDisabled(action: InsertAction) {
|
|
|
272
324
|
return (action === 'launchApp' && hasLaunchAppStep.value) || isInsertActionDisabled(action);
|
|
273
325
|
}
|
|
274
326
|
|
|
327
|
+
function isLaunchExecutionDisabled() {
|
|
328
|
+
return Boolean(props.disabled && !props.allowedLockedActions?.includes('launchApp'));
|
|
329
|
+
}
|
|
330
|
+
|
|
275
331
|
function canOpenInsertMenu() {
|
|
276
332
|
return !props.disabled || Boolean(props.allowedLockedActions?.length);
|
|
277
333
|
}
|
|
278
334
|
|
|
279
335
|
function isInteractiveTarget(target: EventTarget | null) {
|
|
280
|
-
|
|
281
|
-
|
|
336
|
+
if (!(target instanceof Element)) return false;
|
|
337
|
+
if (target.closest('.appium-flow-node__toggle')) return false;
|
|
338
|
+
return Boolean(target.closest(
|
|
339
|
+
'button,input,textarea,select,.el-select,.el-input,.el-input-number',
|
|
282
340
|
));
|
|
283
341
|
}
|
|
284
342
|
|
|
@@ -286,54 +344,68 @@ function resetFlowView() {
|
|
|
286
344
|
flowPan.x = 0;
|
|
287
345
|
flowPan.y = 0;
|
|
288
346
|
flowScale.value = 1;
|
|
347
|
+
overviewPan.x = 0;
|
|
348
|
+
overviewPan.y = 0;
|
|
349
|
+
overviewScale.value = 1;
|
|
289
350
|
}
|
|
290
351
|
|
|
291
|
-
function
|
|
352
|
+
function viewportState(viewport: FlowViewport) {
|
|
353
|
+
return viewport === 'overview'
|
|
354
|
+
? { pan: overviewPan, scale: overviewScale }
|
|
355
|
+
: { pan: flowPan, scale: flowScale };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function handleFlowWheel(event: WheelEvent, viewport: FlowViewport) {
|
|
292
359
|
if (!event.ctrlKey) return;
|
|
293
360
|
event.preventDefault();
|
|
361
|
+
const { pan, scale } = viewportState(viewport);
|
|
294
362
|
const canvas = event.currentTarget as HTMLElement;
|
|
295
363
|
const bounds = canvas.getBoundingClientRect();
|
|
296
364
|
const cursorX = event.clientX - bounds.left;
|
|
297
365
|
const cursorY = event.clientY - bounds.top;
|
|
298
|
-
const previousScale =
|
|
366
|
+
const previousScale = scale.value;
|
|
299
367
|
const direction = event.deltaY < 0 ? 1 : -1;
|
|
300
368
|
const nextScale = Math.min(2, Math.max(0.5, Math.round((previousScale + direction * 0.1) * 10) / 10));
|
|
301
369
|
if (nextScale === previousScale) return;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
370
|
+
pan.x = cursorX - ((cursorX - pan.x) / previousScale) * nextScale;
|
|
371
|
+
pan.y = cursorY - ((cursorY - pan.y) / previousScale) * nextScale;
|
|
372
|
+
scale.value = nextScale;
|
|
305
373
|
}
|
|
306
374
|
|
|
307
375
|
function handleNodeClick(index: number) {
|
|
376
|
+
if (flowDragMoved.value) return;
|
|
308
377
|
toggleExpanded(index);
|
|
309
378
|
}
|
|
310
379
|
|
|
311
|
-
function startFlowDrag(event: PointerEvent) {
|
|
380
|
+
function startFlowDrag(event: PointerEvent, viewport: FlowViewport) {
|
|
312
381
|
if (isInteractiveTarget(event.target)) return;
|
|
313
|
-
|
|
382
|
+
const { pan } = viewportState(viewport);
|
|
383
|
+
draggingViewport.value = viewport;
|
|
314
384
|
flowDragMoved.value = false;
|
|
315
385
|
dragStart = {
|
|
316
386
|
pointerId: event.pointerId,
|
|
317
387
|
x: event.clientX,
|
|
318
388
|
y: event.clientY,
|
|
319
|
-
panX:
|
|
320
|
-
panY:
|
|
389
|
+
panX: pan.x,
|
|
390
|
+
panY: pan.y,
|
|
391
|
+
viewport,
|
|
321
392
|
};
|
|
322
|
-
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
323
393
|
}
|
|
324
394
|
|
|
325
|
-
function moveFlowDrag(event: PointerEvent) {
|
|
326
|
-
if (
|
|
327
|
-
if (Math.abs(event.clientX - dragStart.x) + Math.abs(event.clientY - dragStart.y) > 4) {
|
|
395
|
+
function moveFlowDrag(event: PointerEvent, viewport: FlowViewport) {
|
|
396
|
+
if (draggingViewport.value !== viewport || event.pointerId !== dragStart.pointerId) return;
|
|
397
|
+
if (!flowDragMoved.value && Math.abs(event.clientX - dragStart.x) + Math.abs(event.clientY - dragStart.y) > 4) {
|
|
328
398
|
flowDragMoved.value = true;
|
|
399
|
+
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
329
400
|
}
|
|
330
|
-
|
|
331
|
-
|
|
401
|
+
const { pan } = viewportState(viewport);
|
|
402
|
+
pan.x = dragStart.panX + event.clientX - dragStart.x;
|
|
403
|
+
pan.y = dragStart.panY + event.clientY - dragStart.y;
|
|
332
404
|
}
|
|
333
405
|
|
|
334
|
-
function stopFlowDrag(event: PointerEvent) {
|
|
335
|
-
if (
|
|
336
|
-
|
|
406
|
+
function stopFlowDrag(event: PointerEvent, viewport: FlowViewport) {
|
|
407
|
+
if (draggingViewport.value !== viewport || event.pointerId !== dragStart.pointerId) return;
|
|
408
|
+
draggingViewport.value = null;
|
|
337
409
|
const target = event.currentTarget as HTMLElement;
|
|
338
410
|
if (target.hasPointerCapture(event.pointerId)) target.releasePointerCapture(event.pointerId);
|
|
339
411
|
window.setTimeout(() => {
|
|
@@ -360,19 +432,31 @@ watch(() => props.steps.length, () => {
|
|
|
360
432
|
</div>
|
|
361
433
|
<div
|
|
362
434
|
class="appium-flow-canvas"
|
|
363
|
-
:class="{ 'appium-flow-canvas--dragging':
|
|
364
|
-
@pointerdown="startFlowDrag"
|
|
365
|
-
@pointermove="moveFlowDrag"
|
|
366
|
-
@pointerup="stopFlowDrag"
|
|
367
|
-
@pointercancel="stopFlowDrag"
|
|
368
|
-
@pointerleave="stopFlowDrag"
|
|
369
|
-
@wheel="handleFlowWheel"
|
|
435
|
+
:class="{ 'appium-flow-canvas--dragging': draggingViewport === 'main' }"
|
|
436
|
+
@pointerdown="startFlowDrag($event, 'main')"
|
|
437
|
+
@pointermove="moveFlowDrag($event, 'main')"
|
|
438
|
+
@pointerup="stopFlowDrag($event, 'main')"
|
|
439
|
+
@pointercancel="stopFlowDrag($event, 'main')"
|
|
440
|
+
@pointerleave="stopFlowDrag($event, 'main')"
|
|
441
|
+
@wheel="handleFlowWheel($event, 'main')"
|
|
370
442
|
>
|
|
371
443
|
<div
|
|
372
444
|
class="appium-flow-content"
|
|
373
|
-
:style="{
|
|
445
|
+
:style="{
|
|
446
|
+
transform: `translate(${flowPan.x}px, ${flowPan.y}px) scale(${flowScale})`,
|
|
447
|
+
'--appium-flow-line-width': '3px',
|
|
448
|
+
}"
|
|
374
449
|
>
|
|
375
|
-
<div class="appium-flow-start"
|
|
450
|
+
<div class="appium-flow-start">
|
|
451
|
+
<svg
|
|
452
|
+
v-if="hasFlowSteps"
|
|
453
|
+
class="appium-flow-line appium-flow-start__line"
|
|
454
|
+
viewBox="0 0 10 100"
|
|
455
|
+
preserveAspectRatio="none"
|
|
456
|
+
aria-hidden="true"
|
|
457
|
+
>
|
|
458
|
+
<path d="M5 0 V100" />
|
|
459
|
+
</svg>
|
|
376
460
|
<div class="appium-flow-start__node">开始</div>
|
|
377
461
|
<el-dropdown
|
|
378
462
|
trigger="click"
|
|
@@ -411,7 +495,17 @@ watch(() => props.steps.length, () => {
|
|
|
411
495
|
:key="step.id"
|
|
412
496
|
class="appium-flow-item"
|
|
413
497
|
:class="{ 'appium-flow-item--condition': defaultKind(step) === 'condition' }"
|
|
498
|
+
:style="flowAxisStyle(step)"
|
|
414
499
|
>
|
|
500
|
+
<svg
|
|
501
|
+
v-if="defaultKind(step) !== 'condition' && hasFollowingVisibleStep(step)"
|
|
502
|
+
class="appium-flow-line appium-flow-item__line"
|
|
503
|
+
viewBox="0 0 10 100"
|
|
504
|
+
preserveAspectRatio="none"
|
|
505
|
+
aria-hidden="true"
|
|
506
|
+
>
|
|
507
|
+
<path d="M5 0 V100" />
|
|
508
|
+
</svg>
|
|
415
509
|
<div
|
|
416
510
|
class="appium-flow-node"
|
|
417
511
|
:class="[
|
|
@@ -433,9 +527,20 @@ watch(() => props.steps.length, () => {
|
|
|
433
527
|
<template v-if="step.optional"> · 可选</template>
|
|
434
528
|
<template v-if="stepMeta(step)"> · {{ stepMeta(step) }}</template>
|
|
435
529
|
</span>
|
|
530
|
+
<span v-if="step.note" class="appium-flow-node__note" :title="step.note">{{ step.note }}</span>
|
|
436
531
|
</span>
|
|
437
532
|
</button>
|
|
438
533
|
<span class="appium-flow-node__actions" @click.stop>
|
|
534
|
+
<el-button
|
|
535
|
+
v-if="step.type === 'launchApp'"
|
|
536
|
+
text
|
|
537
|
+
size="small"
|
|
538
|
+
:icon="VideoPlay"
|
|
539
|
+
:loading="launchingStepId === step.id"
|
|
540
|
+
:disabled="isLaunchExecutionDisabled()"
|
|
541
|
+
title="立即执行启动 App"
|
|
542
|
+
@click="$emit('executeStep', index)"
|
|
543
|
+
/>
|
|
439
544
|
<el-button
|
|
440
545
|
v-if="step.type === 'input' || step.type === 'inputIfExists'"
|
|
441
546
|
text
|
|
@@ -458,40 +563,78 @@ watch(() => props.steps.length, () => {
|
|
|
458
563
|
|
|
459
564
|
<div v-if="defaultKind(step) === 'condition'" class="appium-flow-branches">
|
|
460
565
|
<svg class="appium-flow-branch-lines" viewBox="0 0 100 48" preserveAspectRatio="none" aria-hidden="true">
|
|
461
|
-
<path d="
|
|
462
|
-
<path d="
|
|
566
|
+
<path :d="branchPath(step, 'yes')" />
|
|
567
|
+
<path :d="branchPath(step, 'no')" />
|
|
463
568
|
</svg>
|
|
464
|
-
<div
|
|
569
|
+
<div
|
|
570
|
+
class="appium-flow-branch appium-flow-branch--yes"
|
|
571
|
+
:class="{ 'appium-flow-branch--connected': branchConnectionTargetId(step, 'yes') }"
|
|
572
|
+
>
|
|
573
|
+
<svg
|
|
574
|
+
class="appium-flow-line appium-flow-branch__line"
|
|
575
|
+
viewBox="0 0 10 100"
|
|
576
|
+
preserveAspectRatio="none"
|
|
577
|
+
aria-hidden="true"
|
|
578
|
+
>
|
|
579
|
+
<path d="M5 0 V100" />
|
|
580
|
+
</svg>
|
|
465
581
|
<strong class="appium-flow-branch__label">是</strong>
|
|
466
582
|
<div v-if="branchStepItems(step, 'yes').length" class="appium-flow-branch-steps">
|
|
467
583
|
<div
|
|
468
584
|
v-for="item in branchStepItems(step, 'yes')"
|
|
469
585
|
:key="item.step.id"
|
|
470
|
-
class="appium-flow-branch-step"
|
|
586
|
+
class="appium-flow-branch-step-group"
|
|
471
587
|
>
|
|
472
|
-
<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
<
|
|
480
|
-
|
|
588
|
+
<div
|
|
589
|
+
class="appium-flow-node appium-flow-branch-step"
|
|
590
|
+
:class="[
|
|
591
|
+
`appium-flow-node--${defaultKind(item.step)}`,
|
|
592
|
+
{ 'appium-flow-node--expanded': expandedStepIndex === item.index },
|
|
593
|
+
]"
|
|
594
|
+
>
|
|
595
|
+
<button
|
|
596
|
+
type="button"
|
|
597
|
+
class="appium-flow-node__toggle"
|
|
598
|
+
:aria-expanded="expandedStepIndex === item.index"
|
|
599
|
+
@click.stop="handleNodeClick(item.index)"
|
|
600
|
+
>
|
|
601
|
+
<span class="appium-flow-node__index">{{ item.index + 1 }}</span>
|
|
602
|
+
<span class="appium-flow-node__body">
|
|
603
|
+
<span class="appium-flow-node__title">{{ item.step.label }}</span>
|
|
604
|
+
<span class="appium-flow-node__meta">
|
|
605
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
606
|
+
<template v-if="stepMeta(item.step)"> · {{ stepMeta(item.step) }}</template>
|
|
607
|
+
</span>
|
|
608
|
+
<span v-if="item.step.note" class="appium-flow-node__note" :title="item.step.note">{{ item.step.note }}</span>
|
|
609
|
+
</span>
|
|
610
|
+
</button>
|
|
611
|
+
<span class="appium-flow-node__actions" @click.stop>
|
|
612
|
+
<el-button
|
|
613
|
+
text
|
|
614
|
+
size="small"
|
|
615
|
+
:icon="Delete"
|
|
616
|
+
:disabled="disabled"
|
|
617
|
+
title="删除节点"
|
|
618
|
+
@click="$emit('remove', item.index)"
|
|
619
|
+
/>
|
|
481
620
|
</span>
|
|
482
|
-
</
|
|
483
|
-
<
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
:icon="Delete"
|
|
621
|
+
</div>
|
|
622
|
+
<FlowStepEditor
|
|
623
|
+
v-if="expandedStepIndex === item.index"
|
|
624
|
+
:step="item.step"
|
|
625
|
+
:index="item.index"
|
|
488
626
|
:disabled="disabled"
|
|
489
|
-
|
|
490
|
-
@click.stop="$emit('remove', item.index)"
|
|
627
|
+
@update="updateStep"
|
|
491
628
|
/>
|
|
492
629
|
</div>
|
|
493
630
|
</div>
|
|
494
|
-
<span
|
|
631
|
+
<span
|
|
632
|
+
v-if="!branchStepItems(step, 'yes').length && !branchConnectionTargetId(step, 'yes')"
|
|
633
|
+
class="appium-flow-branch__target"
|
|
634
|
+
>
|
|
635
|
+
{{ targetLabel(step.flow?.yesTargetId) }}
|
|
636
|
+
</span>
|
|
637
|
+
<div class="appium-flow-branch__actions">
|
|
495
638
|
<el-dropdown
|
|
496
639
|
trigger="click"
|
|
497
640
|
:disabled="!canOpenInsertMenu()"
|
|
@@ -524,38 +667,88 @@ watch(() => props.steps.length, () => {
|
|
|
524
667
|
</el-dropdown-menu>
|
|
525
668
|
</template>
|
|
526
669
|
</el-dropdown>
|
|
670
|
+
<el-button
|
|
671
|
+
v-if="shouldShowBranchConnect(index, step, 'yes')"
|
|
672
|
+
size="small"
|
|
673
|
+
class="appium-flow-branch__connect"
|
|
674
|
+
:icon="Link"
|
|
675
|
+
:disabled="disabled"
|
|
676
|
+
:title="branchConnectionTargetId(step, 'yes') ? '取消当前分支与主流程的连接' : '自动连接判断节点之后的主流程节点'"
|
|
677
|
+
@click.stop="toggleBranchConnection(index, step, 'yes')"
|
|
678
|
+
>
|
|
679
|
+
{{ branchConnectionTargetId(step, 'yes') ? '取消连接' : '连接到下一节点' }}
|
|
680
|
+
</el-button>
|
|
681
|
+
</div>
|
|
527
682
|
</div>
|
|
528
|
-
<div
|
|
683
|
+
<div
|
|
684
|
+
class="appium-flow-branch appium-flow-branch--no"
|
|
685
|
+
:class="{ 'appium-flow-branch--connected': branchConnectionTargetId(step, 'no') }"
|
|
686
|
+
>
|
|
687
|
+
<svg
|
|
688
|
+
class="appium-flow-line appium-flow-branch__line"
|
|
689
|
+
viewBox="0 0 10 100"
|
|
690
|
+
preserveAspectRatio="none"
|
|
691
|
+
aria-hidden="true"
|
|
692
|
+
>
|
|
693
|
+
<path d="M5 0 V100" />
|
|
694
|
+
</svg>
|
|
529
695
|
<strong class="appium-flow-branch__label">否</strong>
|
|
530
696
|
<div v-if="branchStepItems(step, 'no').length" class="appium-flow-branch-steps">
|
|
531
697
|
<div
|
|
532
698
|
v-for="item in branchStepItems(step, 'no')"
|
|
533
699
|
:key="item.step.id"
|
|
534
|
-
class="appium-flow-branch-step"
|
|
700
|
+
class="appium-flow-branch-step-group"
|
|
535
701
|
>
|
|
536
|
-
<
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
<
|
|
544
|
-
|
|
702
|
+
<div
|
|
703
|
+
class="appium-flow-node appium-flow-branch-step"
|
|
704
|
+
:class="[
|
|
705
|
+
`appium-flow-node--${defaultKind(item.step)}`,
|
|
706
|
+
{ 'appium-flow-node--expanded': expandedStepIndex === item.index },
|
|
707
|
+
]"
|
|
708
|
+
>
|
|
709
|
+
<button
|
|
710
|
+
type="button"
|
|
711
|
+
class="appium-flow-node__toggle"
|
|
712
|
+
:aria-expanded="expandedStepIndex === item.index"
|
|
713
|
+
@click.stop="handleNodeClick(item.index)"
|
|
714
|
+
>
|
|
715
|
+
<span class="appium-flow-node__index">{{ item.index + 1 }}</span>
|
|
716
|
+
<span class="appium-flow-node__body">
|
|
717
|
+
<span class="appium-flow-node__title">{{ item.step.label }}</span>
|
|
718
|
+
<span class="appium-flow-node__meta">
|
|
719
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
720
|
+
<template v-if="stepMeta(item.step)"> · {{ stepMeta(item.step) }}</template>
|
|
721
|
+
</span>
|
|
722
|
+
<span v-if="item.step.note" class="appium-flow-node__note" :title="item.step.note">{{ item.step.note }}</span>
|
|
723
|
+
</span>
|
|
724
|
+
</button>
|
|
725
|
+
<span class="appium-flow-node__actions" @click.stop>
|
|
726
|
+
<el-button
|
|
727
|
+
text
|
|
728
|
+
size="small"
|
|
729
|
+
:icon="Delete"
|
|
730
|
+
:disabled="disabled"
|
|
731
|
+
title="删除节点"
|
|
732
|
+
@click="$emit('remove', item.index)"
|
|
733
|
+
/>
|
|
545
734
|
</span>
|
|
546
|
-
</
|
|
547
|
-
<
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
:icon="Delete"
|
|
735
|
+
</div>
|
|
736
|
+
<FlowStepEditor
|
|
737
|
+
v-if="expandedStepIndex === item.index"
|
|
738
|
+
:step="item.step"
|
|
739
|
+
:index="item.index"
|
|
552
740
|
:disabled="disabled"
|
|
553
|
-
|
|
554
|
-
@click.stop="$emit('remove', item.index)"
|
|
741
|
+
@update="updateStep"
|
|
555
742
|
/>
|
|
556
743
|
</div>
|
|
557
744
|
</div>
|
|
558
|
-
<span
|
|
745
|
+
<span
|
|
746
|
+
v-if="!branchStepItems(step, 'no').length && !branchConnectionTargetId(step, 'no')"
|
|
747
|
+
class="appium-flow-branch__target"
|
|
748
|
+
>
|
|
749
|
+
{{ targetLabel(step.flow?.noTargetId) }}
|
|
750
|
+
</span>
|
|
751
|
+
<div class="appium-flow-branch__actions">
|
|
559
752
|
<el-dropdown
|
|
560
753
|
trigger="click"
|
|
561
754
|
:disabled="!canOpenInsertMenu()"
|
|
@@ -588,142 +781,44 @@ watch(() => props.steps.length, () => {
|
|
|
588
781
|
</el-dropdown-menu>
|
|
589
782
|
</template>
|
|
590
783
|
</el-dropdown>
|
|
784
|
+
<el-button
|
|
785
|
+
v-if="shouldShowBranchConnect(index, step, 'no')"
|
|
786
|
+
size="small"
|
|
787
|
+
class="appium-flow-branch__connect"
|
|
788
|
+
:icon="Link"
|
|
789
|
+
:disabled="disabled"
|
|
790
|
+
:title="branchConnectionTargetId(step, 'no') ? '取消当前分支与主流程的连接' : '自动连接判断节点之后的主流程节点'"
|
|
791
|
+
@click.stop="toggleBranchConnection(index, step, 'no')"
|
|
792
|
+
>
|
|
793
|
+
{{ branchConnectionTargetId(step, 'no') ? '取消连接' : '连接到下一节点' }}
|
|
794
|
+
</el-button>
|
|
795
|
+
</div>
|
|
591
796
|
</div>
|
|
592
797
|
</div>
|
|
593
798
|
|
|
594
|
-
<div v-if="
|
|
595
|
-
<
|
|
596
|
-
<
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
:disabled="disabled"
|
|
608
|
-
@update:model-value="patchFlow(index, { nodeKind: $event as FlowKind })"
|
|
609
|
-
>
|
|
610
|
-
<el-option label="操作" value="action" />
|
|
611
|
-
<el-option label="判断" value="condition" />
|
|
612
|
-
<el-option label="校验" value="assertion" />
|
|
613
|
-
</el-select>
|
|
614
|
-
</el-form-item>
|
|
615
|
-
<el-form-item label="超时时间 ms">
|
|
616
|
-
<el-input-number
|
|
617
|
-
:model-value="step.timeoutMs || undefined"
|
|
618
|
-
:disabled="disabled"
|
|
619
|
-
:min="0"
|
|
620
|
-
:max="999999"
|
|
621
|
-
controls-position="right"
|
|
622
|
-
@update:model-value="patchStep(index, { timeoutMs: Number($event || 0) || undefined })"
|
|
623
|
-
/>
|
|
624
|
-
</el-form-item>
|
|
625
|
-
</div>
|
|
626
|
-
<el-form-item v-if="step.type === 'input' || step.type === 'inputIfExists' || step.type === 'assertText'" label="文本内容">
|
|
627
|
-
<el-input
|
|
628
|
-
:model-value="step.value || ''"
|
|
629
|
-
:disabled="disabled"
|
|
630
|
-
@update:model-value="patchStep(index, { value: String($event) })"
|
|
631
|
-
/>
|
|
632
|
-
</el-form-item>
|
|
633
|
-
<div v-if="step.selector" class="appium-flow-editor__grid">
|
|
634
|
-
<el-form-item label="Selector 类型">
|
|
635
|
-
<el-select
|
|
636
|
-
:model-value="step.selector.strategy"
|
|
637
|
-
:disabled="disabled"
|
|
638
|
-
@update:model-value="patchStep(index, { selector: { ...step.selector!, strategy: $event as AppiumSelector['strategy'] } })"
|
|
639
|
-
>
|
|
640
|
-
<el-option label="accessibility id" value="accessibilityId" />
|
|
641
|
-
<el-option label="id" value="id" />
|
|
642
|
-
<el-option label="android uiAutomator" value="androidUiAutomator" />
|
|
643
|
-
<el-option label="xpath" value="xpath" />
|
|
644
|
-
</el-select>
|
|
645
|
-
</el-form-item>
|
|
646
|
-
<el-form-item label="Selector 值">
|
|
647
|
-
<el-input
|
|
648
|
-
:model-value="step.selector.value || ''"
|
|
649
|
-
:disabled="disabled"
|
|
650
|
-
@update:model-value="patchStep(index, { selector: { ...step.selector!, value: String($event) } })"
|
|
651
|
-
/>
|
|
652
|
-
</el-form-item>
|
|
653
|
-
</div>
|
|
654
|
-
<div v-if="step.selector" class="appium-flow-editor__grid">
|
|
655
|
-
<el-form-item label="父级上下文类型">
|
|
656
|
-
<el-select
|
|
657
|
-
:model-value="step.contextSelector?.strategy || ''"
|
|
658
|
-
:disabled="disabled"
|
|
659
|
-
clearable
|
|
660
|
-
placeholder="不使用父级"
|
|
661
|
-
@update:model-value="patchStep(index, { contextSelector: $event ? { ...(step.contextSelector || { value: '' }), strategy: $event as AppiumSelector['strategy'] } : undefined })"
|
|
662
|
-
>
|
|
663
|
-
<el-option label="accessibility id" value="accessibilityId" />
|
|
664
|
-
<el-option label="id" value="id" />
|
|
665
|
-
<el-option label="android uiAutomator" value="androidUiAutomator" />
|
|
666
|
-
<el-option label="xpath" value="xpath" />
|
|
667
|
-
</el-select>
|
|
668
|
-
</el-form-item>
|
|
669
|
-
<el-form-item label="父级上下文值">
|
|
670
|
-
<el-input
|
|
671
|
-
:model-value="step.contextSelector?.value || ''"
|
|
672
|
-
:disabled="disabled || !step.contextSelector"
|
|
673
|
-
@update:model-value="patchStep(index, { contextSelector: { ...(step.contextSelector || { strategy: 'xpath' }), value: String($event) } })"
|
|
674
|
-
/>
|
|
675
|
-
</el-form-item>
|
|
676
|
-
</div>
|
|
677
|
-
<el-form-item label="可选步骤">
|
|
678
|
-
<el-switch
|
|
679
|
-
:model-value="Boolean(step.optional)"
|
|
680
|
-
:disabled="disabled"
|
|
681
|
-
active-text="找不到时跳过"
|
|
682
|
-
inactive-text="找不到时失败"
|
|
683
|
-
@update:model-value="patchStep(index, { optional: Boolean($event) })"
|
|
684
|
-
/>
|
|
685
|
-
</el-form-item>
|
|
686
|
-
<template v-if="defaultKind(step) === 'condition'">
|
|
687
|
-
<div class="appium-flow-editor__grid">
|
|
688
|
-
<el-form-item label="是,进入">
|
|
689
|
-
<el-select
|
|
690
|
-
:model-value="step.flow?.yesTargetId || ''"
|
|
691
|
-
:disabled="disabled"
|
|
692
|
-
clearable
|
|
693
|
-
placeholder="默认下一步"
|
|
694
|
-
@update:model-value="patchFlow(index, { yesTargetId: String($event || '') })"
|
|
695
|
-
>
|
|
696
|
-
<el-option v-for="option in scopedStepOptions()" :key="option.value" :label="option.label" :value="option.value" />
|
|
697
|
-
</el-select>
|
|
698
|
-
</el-form-item>
|
|
699
|
-
<el-form-item label="否,进入">
|
|
700
|
-
<el-select
|
|
701
|
-
:model-value="step.flow?.noTargetId || ''"
|
|
702
|
-
:disabled="disabled"
|
|
703
|
-
clearable
|
|
704
|
-
placeholder="默认结束"
|
|
705
|
-
@update:model-value="patchFlow(index, { noTargetId: String($event || '') })"
|
|
706
|
-
>
|
|
707
|
-
<el-option v-for="option in scopedStepOptions()" :key="option.value" :label="option.label" :value="option.value" />
|
|
708
|
-
</el-select>
|
|
709
|
-
</el-form-item>
|
|
710
|
-
</div>
|
|
711
|
-
</template>
|
|
712
|
-
<div class="appium-flow-editor__actions">
|
|
713
|
-
<el-button :icon="Timer" :disabled="disabled" @click="$emit('addDelay', index)">
|
|
714
|
-
插入延时
|
|
715
|
-
</el-button>
|
|
716
|
-
<el-button
|
|
717
|
-
v-if="defaultKind(step) !== 'condition'"
|
|
718
|
-
:disabled="disabled"
|
|
719
|
-
@click="patchFlow(index, { nodeKind: 'condition', yesTargetId: '', noTargetId: '' })"
|
|
720
|
-
>
|
|
721
|
-
转为判断节点
|
|
722
|
-
</el-button>
|
|
723
|
-
</div>
|
|
724
|
-
</el-form>
|
|
799
|
+
<div v-if="hasBranchConnection(step)" class="appium-flow-merge">
|
|
800
|
+
<svg viewBox="0 0 100 44" preserveAspectRatio="none" aria-hidden="true">
|
|
801
|
+
<path
|
|
802
|
+
v-if="branchConnectionTargetId(step, 'yes')"
|
|
803
|
+
class="appium-flow-merge__yes"
|
|
804
|
+
:d="mergePath(step, 'yes')"
|
|
805
|
+
/>
|
|
806
|
+
<path
|
|
807
|
+
v-if="branchConnectionTargetId(step, 'no')"
|
|
808
|
+
class="appium-flow-merge__no"
|
|
809
|
+
:d="mergePath(step, 'no')"
|
|
810
|
+
/>
|
|
811
|
+
</svg>
|
|
725
812
|
</div>
|
|
726
813
|
|
|
814
|
+
<FlowStepEditor
|
|
815
|
+
v-if="expandedStepIndex === index"
|
|
816
|
+
:step="step"
|
|
817
|
+
:index="index"
|
|
818
|
+
:disabled="disabled"
|
|
819
|
+
@update="updateStep"
|
|
820
|
+
/>
|
|
821
|
+
|
|
727
822
|
<el-dropdown
|
|
728
823
|
v-if="defaultKind(step) !== 'condition'"
|
|
729
824
|
trigger="click"
|
|
@@ -766,12 +861,33 @@ watch(() => props.steps.length, () => {
|
|
|
766
861
|
class="appium-flow-dialog"
|
|
767
862
|
append-to-body
|
|
768
863
|
>
|
|
769
|
-
<div
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
864
|
+
<div
|
|
865
|
+
class="appium-flow-dialog-canvas"
|
|
866
|
+
:class="{ 'appium-flow-dialog-canvas--dragging': draggingViewport === 'overview' }"
|
|
867
|
+
@pointerdown="startFlowDrag($event, 'overview')"
|
|
868
|
+
@pointermove="moveFlowDrag($event, 'overview')"
|
|
869
|
+
@pointerup="stopFlowDrag($event, 'overview')"
|
|
870
|
+
@pointercancel="stopFlowDrag($event, 'overview')"
|
|
871
|
+
@pointerleave="stopFlowDrag($event, 'overview')"
|
|
872
|
+
@wheel="handleFlowWheel($event, 'overview')"
|
|
873
|
+
>
|
|
874
|
+
<div
|
|
875
|
+
class="appium-flow-content appium-flow-content--overview"
|
|
876
|
+
:style="{
|
|
877
|
+
transform: `translate(${overviewPan.x}px, ${overviewPan.y}px) scale(${overviewScale})`,
|
|
878
|
+
'--appium-flow-line-width': '3px',
|
|
879
|
+
}"
|
|
880
|
+
>
|
|
881
|
+
<div class="appium-flow-start appium-flow-start--overview">
|
|
882
|
+
<svg
|
|
883
|
+
v-if="hasFlowSteps"
|
|
884
|
+
class="appium-flow-line appium-flow-start__line"
|
|
885
|
+
viewBox="0 0 10 100"
|
|
886
|
+
preserveAspectRatio="none"
|
|
887
|
+
aria-hidden="true"
|
|
888
|
+
>
|
|
889
|
+
<path d="M5 0 V100" />
|
|
890
|
+
</svg>
|
|
775
891
|
<div class="appium-flow-start__node">开始</div>
|
|
776
892
|
</div>
|
|
777
893
|
<div
|
|
@@ -779,12 +895,30 @@ watch(() => props.steps.length, () => {
|
|
|
779
895
|
:key="step.id"
|
|
780
896
|
class="appium-flow-item"
|
|
781
897
|
:class="{ 'appium-flow-item--condition': defaultKind(step) === 'condition' }"
|
|
898
|
+
:style="flowAxisStyle(step)"
|
|
782
899
|
>
|
|
900
|
+
<svg
|
|
901
|
+
v-if="defaultKind(step) !== 'condition' && hasFollowingVisibleStep(step)"
|
|
902
|
+
class="appium-flow-line appium-flow-item__line"
|
|
903
|
+
viewBox="0 0 10 100"
|
|
904
|
+
preserveAspectRatio="none"
|
|
905
|
+
aria-hidden="true"
|
|
906
|
+
>
|
|
907
|
+
<path d="M5 0 V100" />
|
|
908
|
+
</svg>
|
|
783
909
|
<div
|
|
784
910
|
class="appium-flow-node"
|
|
785
|
-
:class="
|
|
911
|
+
:class="[
|
|
912
|
+
`appium-flow-node--${defaultKind(step)}`,
|
|
913
|
+
{ 'appium-flow-node--expanded': expandedStepIndex === index },
|
|
914
|
+
]"
|
|
786
915
|
>
|
|
787
|
-
<
|
|
916
|
+
<button
|
|
917
|
+
type="button"
|
|
918
|
+
class="appium-flow-node__toggle"
|
|
919
|
+
:aria-expanded="expandedStepIndex === index"
|
|
920
|
+
@click="handleNodeClick(index)"
|
|
921
|
+
>
|
|
788
922
|
<span class="appium-flow-node__index">{{ index + 1 }}</span>
|
|
789
923
|
<span class="appium-flow-node__body">
|
|
790
924
|
<span class="appium-flow-node__title" :title="step.label">{{ step.label }}</span>
|
|
@@ -793,54 +927,157 @@ watch(() => props.steps.length, () => {
|
|
|
793
927
|
<template v-if="step.optional"> · 可选</template>
|
|
794
928
|
<template v-if="stepMeta(step)"> · {{ stepMeta(step) }}</template>
|
|
795
929
|
</span>
|
|
930
|
+
<span v-if="step.note" class="appium-flow-node__note" :title="step.note">{{ step.note }}</span>
|
|
796
931
|
</span>
|
|
797
|
-
</
|
|
932
|
+
</button>
|
|
798
933
|
</div>
|
|
799
934
|
|
|
935
|
+
<FlowStepEditor
|
|
936
|
+
v-if="expandedStepIndex === index"
|
|
937
|
+
:step="step"
|
|
938
|
+
:index="index"
|
|
939
|
+
:disabled="disabled"
|
|
940
|
+
@update="updateStep"
|
|
941
|
+
/>
|
|
942
|
+
|
|
800
943
|
<div v-if="defaultKind(step) === 'condition'" class="appium-flow-branches">
|
|
801
944
|
<svg class="appium-flow-branch-lines" viewBox="0 0 100 48" preserveAspectRatio="none" aria-hidden="true">
|
|
802
|
-
<path d="
|
|
803
|
-
<path d="
|
|
945
|
+
<path :d="branchPath(step, 'yes')" />
|
|
946
|
+
<path :d="branchPath(step, 'no')" />
|
|
804
947
|
</svg>
|
|
805
|
-
<div
|
|
948
|
+
<div
|
|
949
|
+
class="appium-flow-branch appium-flow-branch--yes"
|
|
950
|
+
:class="{ 'appium-flow-branch--connected': branchConnectionTargetId(step, 'yes') }"
|
|
951
|
+
>
|
|
952
|
+
<svg
|
|
953
|
+
class="appium-flow-line appium-flow-branch__line"
|
|
954
|
+
viewBox="0 0 10 100"
|
|
955
|
+
preserveAspectRatio="none"
|
|
956
|
+
aria-hidden="true"
|
|
957
|
+
>
|
|
958
|
+
<path d="M5 0 V100" />
|
|
959
|
+
</svg>
|
|
806
960
|
<strong class="appium-flow-branch__label">是</strong>
|
|
807
961
|
<div v-if="branchStepItems(step, 'yes').length" class="appium-flow-branch-steps">
|
|
808
962
|
<div
|
|
809
963
|
v-for="item in branchStepItems(step, 'yes')"
|
|
810
964
|
:key="item.step.id"
|
|
811
|
-
class="appium-flow-branch-step"
|
|
965
|
+
class="appium-flow-branch-step-group"
|
|
812
966
|
>
|
|
813
|
-
<
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
{
|
|
818
|
-
|
|
819
|
-
|
|
967
|
+
<div
|
|
968
|
+
class="appium-flow-node appium-flow-branch-step"
|
|
969
|
+
:class="[
|
|
970
|
+
`appium-flow-node--${defaultKind(item.step)}`,
|
|
971
|
+
{ 'appium-flow-node--expanded': expandedStepIndex === item.index },
|
|
972
|
+
]"
|
|
973
|
+
>
|
|
974
|
+
<button
|
|
975
|
+
type="button"
|
|
976
|
+
class="appium-flow-node__toggle"
|
|
977
|
+
:aria-expanded="expandedStepIndex === item.index"
|
|
978
|
+
@click="handleNodeClick(item.index)"
|
|
979
|
+
>
|
|
980
|
+
<span class="appium-flow-node__index">{{ item.index + 1 }}</span>
|
|
981
|
+
<span class="appium-flow-node__body">
|
|
982
|
+
<span class="appium-flow-node__title">{{ item.step.label }}</span>
|
|
983
|
+
<span class="appium-flow-node__meta">
|
|
984
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
985
|
+
<template v-if="stepMeta(item.step)"> · {{ stepMeta(item.step) }}</template>
|
|
986
|
+
</span>
|
|
987
|
+
<span v-if="item.step.note" class="appium-flow-node__note" :title="item.step.note">{{ item.step.note }}</span>
|
|
988
|
+
</span>
|
|
989
|
+
</button>
|
|
990
|
+
</div>
|
|
991
|
+
<FlowStepEditor
|
|
992
|
+
v-if="expandedStepIndex === item.index"
|
|
993
|
+
:step="item.step"
|
|
994
|
+
:index="item.index"
|
|
995
|
+
:disabled="disabled"
|
|
996
|
+
@update="updateStep"
|
|
997
|
+
/>
|
|
820
998
|
</div>
|
|
821
999
|
</div>
|
|
822
|
-
<span
|
|
1000
|
+
<span
|
|
1001
|
+
v-if="!branchStepItems(step, 'yes').length && !branchConnectionTargetId(step, 'yes')"
|
|
1002
|
+
class="appium-flow-branch__target"
|
|
1003
|
+
>
|
|
1004
|
+
{{ targetLabel(step.flow?.yesTargetId) }}
|
|
1005
|
+
</span>
|
|
823
1006
|
</div>
|
|
824
|
-
<div
|
|
1007
|
+
<div
|
|
1008
|
+
class="appium-flow-branch appium-flow-branch--no"
|
|
1009
|
+
:class="{ 'appium-flow-branch--connected': branchConnectionTargetId(step, 'no') }"
|
|
1010
|
+
>
|
|
1011
|
+
<svg
|
|
1012
|
+
class="appium-flow-line appium-flow-branch__line"
|
|
1013
|
+
viewBox="0 0 10 100"
|
|
1014
|
+
preserveAspectRatio="none"
|
|
1015
|
+
aria-hidden="true"
|
|
1016
|
+
>
|
|
1017
|
+
<path d="M5 0 V100" />
|
|
1018
|
+
</svg>
|
|
825
1019
|
<strong class="appium-flow-branch__label">否</strong>
|
|
826
1020
|
<div v-if="branchStepItems(step, 'no').length" class="appium-flow-branch-steps">
|
|
827
1021
|
<div
|
|
828
1022
|
v-for="item in branchStepItems(step, 'no')"
|
|
829
1023
|
:key="item.step.id"
|
|
830
|
-
class="appium-flow-branch-step"
|
|
1024
|
+
class="appium-flow-branch-step-group"
|
|
831
1025
|
>
|
|
832
|
-
<
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
{
|
|
837
|
-
|
|
838
|
-
|
|
1026
|
+
<div
|
|
1027
|
+
class="appium-flow-node appium-flow-branch-step"
|
|
1028
|
+
:class="[
|
|
1029
|
+
`appium-flow-node--${defaultKind(item.step)}`,
|
|
1030
|
+
{ 'appium-flow-node--expanded': expandedStepIndex === item.index },
|
|
1031
|
+
]"
|
|
1032
|
+
>
|
|
1033
|
+
<button
|
|
1034
|
+
type="button"
|
|
1035
|
+
class="appium-flow-node__toggle"
|
|
1036
|
+
:aria-expanded="expandedStepIndex === item.index"
|
|
1037
|
+
@click="handleNodeClick(item.index)"
|
|
1038
|
+
>
|
|
1039
|
+
<span class="appium-flow-node__index">{{ item.index + 1 }}</span>
|
|
1040
|
+
<span class="appium-flow-node__body">
|
|
1041
|
+
<span class="appium-flow-node__title">{{ item.step.label }}</span>
|
|
1042
|
+
<span class="appium-flow-node__meta">
|
|
1043
|
+
{{ kindLabel(defaultKind(item.step)) }} · {{ typeLabel(item.step) }}
|
|
1044
|
+
<template v-if="stepMeta(item.step)"> · {{ stepMeta(item.step) }}</template>
|
|
1045
|
+
</span>
|
|
1046
|
+
<span v-if="item.step.note" class="appium-flow-node__note" :title="item.step.note">{{ item.step.note }}</span>
|
|
1047
|
+
</span>
|
|
1048
|
+
</button>
|
|
1049
|
+
</div>
|
|
1050
|
+
<FlowStepEditor
|
|
1051
|
+
v-if="expandedStepIndex === item.index"
|
|
1052
|
+
:step="item.step"
|
|
1053
|
+
:index="item.index"
|
|
1054
|
+
:disabled="disabled"
|
|
1055
|
+
@update="updateStep"
|
|
1056
|
+
/>
|
|
839
1057
|
</div>
|
|
840
1058
|
</div>
|
|
841
|
-
<span
|
|
1059
|
+
<span
|
|
1060
|
+
v-if="!branchStepItems(step, 'no').length && !branchConnectionTargetId(step, 'no')"
|
|
1061
|
+
class="appium-flow-branch__target"
|
|
1062
|
+
>
|
|
1063
|
+
{{ targetLabel(step.flow?.noTargetId) }}
|
|
1064
|
+
</span>
|
|
842
1065
|
</div>
|
|
843
1066
|
</div>
|
|
1067
|
+
<div v-if="hasBranchConnection(step)" class="appium-flow-merge">
|
|
1068
|
+
<svg viewBox="0 0 100 44" preserveAspectRatio="none" aria-hidden="true">
|
|
1069
|
+
<path
|
|
1070
|
+
v-if="branchConnectionTargetId(step, 'yes')"
|
|
1071
|
+
class="appium-flow-merge__yes"
|
|
1072
|
+
:d="mergePath(step, 'yes')"
|
|
1073
|
+
/>
|
|
1074
|
+
<path
|
|
1075
|
+
v-if="branchConnectionTargetId(step, 'no')"
|
|
1076
|
+
class="appium-flow-merge__no"
|
|
1077
|
+
:d="mergePath(step, 'no')"
|
|
1078
|
+
/>
|
|
1079
|
+
</svg>
|
|
1080
|
+
</div>
|
|
844
1081
|
</div>
|
|
845
1082
|
</div>
|
|
846
1083
|
</div>
|