@coda-console/testbed 1.2.0 → 1.3.0

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.
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <!-- 使用测试机(README.md):测试机的唯一入口。先从本机的设备登记查出这台设备,再排进它的组 device:<设备名>,
3
3
  按需装上 App,把任务交给调用的会话;会话做任务期间,它用 host-sim run --kind <设备> 的命令(能力 run-on-device)借用这个组。
4
- 任务答复、装包或任务出错、运行被取消,都先等借去的命令停完、再切黑屏,然后放开;切不过去就请用户处理,处理之前一直占着。
4
+ 任务答复、装包或任务出错、运行被取消,都先等借去的命令停完、常驻测试机再切黑屏,个人设备直接放开;切不过去就请用户处理,处理之前一直占着。
5
5
  能力:只被调用(workflow_run 或 callActivity),结果交给调用者。 -->
6
6
  <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
7
7
  xmlns:coda="https://coda.pub/schema/bpmn/1.0" id="use-testbed-definitions" targetNamespace="https://coda.pub/plugins/testbed">
@@ -22,6 +22,7 @@
22
22
  <coda:output name="name" />
23
23
  <coda:output name="udid" />
24
24
  <coda:output name="note" />
25
+ <coda:output name="personal" />
25
26
  </extensionElements>
26
27
  <script>node "$CODA_PLUGIN_PATH/scripts/device.mjs"</script>
27
28
  </scriptTask>
@@ -56,21 +57,27 @@
56
57
  <userTask id="task" name="=inputs.task" coda:status="{{find.name}} 交给会话使用">
57
58
  <documentation>{{inputs.instructions}}
58
59
 
59
- 测试机 {{find.name}}(设备名 {{inputs.device}},UDID {{find.udid}})由这个任务占着,直到答复:本会话(含子 Agent)用 host-sim run --kind {{inputs.device}} -- &lt;命令&gt; 操作它,命令结束不会交出设备。答复之后还在执行的命令随之停下,测试机切黑屏,再交给下一位。
60
+ 测试机 {{find.name}}(设备名 {{inputs.device}},UDID {{find.udid}})由这个任务占着,直到答复:本会话(含子 Agent)用 host-sim run --kind {{inputs.device}} -- &lt;命令&gt; 操作它,命令结束不会交出设备。答复之后还在执行的命令随之停下,{{if find.personal then "个人设备直接交给下一位。" else "测试机切黑屏,再交给下一位。"}}
60
61
 
61
62
  {{find.note}}</documentation>
62
63
  </userTask>
63
- <sequenceFlow id="used" sourceRef="task" targetRef="black" />
64
+ <sequenceFlow id="used" sourceRef="task" targetRef="return-device" />
64
65
 
65
- <!-- 装包或任务出错、运行被取消:同样先切黑屏,再让运行以原来的原因结束。 -->
66
+ <!-- 装包或任务出错、运行被取消:个人设备直接归还,常驻测试机先切黑屏,再让运行以原来的原因结束。 -->
66
67
  <boundaryEvent id="install-cancelled" attachedToRef="install"><messageEventDefinition messageRef="cancel" /></boundaryEvent>
67
68
  <boundaryEvent id="install-failed" attachedToRef="install"><errorEventDefinition /></boundaryEvent>
68
69
  <boundaryEvent id="task-cancelled" attachedToRef="task"><messageEventDefinition messageRef="cancel" /></boundaryEvent>
69
70
  <boundaryEvent id="task-failed" attachedToRef="task"><errorEventDefinition /></boundaryEvent>
70
- <sequenceFlow id="install-cancelled-black" sourceRef="install-cancelled" targetRef="black-stop" />
71
- <sequenceFlow id="install-failed-black" sourceRef="install-failed" targetRef="black-stop" />
72
- <sequenceFlow id="task-cancelled-black" sourceRef="task-cancelled" targetRef="black-stop" />
73
- <sequenceFlow id="task-failed-black" sourceRef="task-failed" targetRef="black-stop" />
71
+ <sequenceFlow id="install-cancelled-black" sourceRef="install-cancelled" targetRef="return-stopped" />
72
+ <sequenceFlow id="install-failed-black" sourceRef="install-failed" targetRef="return-stopped" />
73
+ <sequenceFlow id="task-cancelled-black" sourceRef="task-cancelled" targetRef="return-stopped" />
74
+ <sequenceFlow id="task-failed-black" sourceRef="task-failed" targetRef="return-stopped" />
75
+
76
+ <exclusiveGateway id="return-device" default="return-device-resident" />
77
+ <sequenceFlow id="return-device-personal" sourceRef="return-device" targetRef="done">
78
+ <conditionExpression xsi:type="tFormalExpression">=find.personal</conditionExpression>
79
+ </sequenceFlow>
80
+ <sequenceFlow id="return-device-resident" sourceRef="return-device" targetRef="black" />
74
81
 
75
82
  <scriptTask id="black" name="切黑屏" coda:checkout="false" coda:timeout="2" coda:status="{{find.name}} 切黑屏" scriptFormat="bash">
76
83
  <extensionElements>
@@ -106,6 +113,12 @@
106
113
  <extensionElements><coda:output name="stopped" value="false" /></extensionElements>
107
114
  </endEvent>
108
115
 
116
+ <exclusiveGateway id="return-stopped" default="return-stopped-resident" />
117
+ <sequenceFlow id="return-stopped-personal" sourceRef="return-stopped" targetRef="stopped">
118
+ <conditionExpression xsi:type="tFormalExpression">=find.personal</conditionExpression>
119
+ </sequenceFlow>
120
+ <sequenceFlow id="return-stopped-resident" sourceRef="return-stopped" targetRef="black-stop" />
121
+
109
122
  <scriptTask id="black-stop" name="切黑屏(没有做完)" coda:checkout="false" coda:timeout="2" coda:status="{{find.name}} 切黑屏" scriptFormat="bash">
110
123
  <extensionElements>
111
124
  <coda:env name="DEVICE" value="=inputs.device" />
package/README.md CHANGED
@@ -13,8 +13,9 @@ iPhone、iPad 测试机由这个插件登记归属。每台设备是一个[并
13
13
  [{ "id": "iphone-testbed", "name": "iPhone13pm", "udid": "…", "note": "…" }]
14
14
  ```
15
15
 
16
+ `personal` 可省;`"personal": true` 表示维护者的个人设备,归还时不切黑屏。缺省为常驻测试机,沿用切黑屏行为。空间声明的属性均为文本,`personal` 发布为 `"true"` 或 `"false"`;读取时归一为布尔值。
16
17
  `id` 是设备名(小写字母开头,字母、数字、连字符),`name` 显示名,`udid` 不重复;`note` 可省,是给拿到任务的会话的说明(这台 Mac 上怎么驱动这台设备、哪些东西不能碰),原样附在任务末尾。
17
- 登记在哪个实例就归哪个实例,`devices.json` 是归属的唯一事实来源。插件启动时声明 `device:<id>`,把 `name`、`udid`、`note` 发布到空间,以 `udid` 为唯一属性;文件变化时重新全量声明。空间里同一设备只能登记一次,同名或相同 UDID 的重复登记会使占用失败并报冲突。
18
+ 登记在哪个实例就归哪个实例,`devices.json` 是归属的唯一事实来源。插件启动时声明 `device:<id>`,把 `name`、`udid`、`note`、`personal` 发布到空间,以 `udid` 为唯一属性;文件变化时重新全量声明。空间里同一设备只能登记一次,同名或相同 UDID 的重复登记会使占用失败并报冲突。
18
19
  登记读不了时日志记录错误,保留已有声明;有效的空数组 `[]` 撤销全部声明。设备正被使用时不要改名或换 UDID。
19
20
 
20
21
  任何实例的会话都能用设备,由引擎在归属实例排队。安装、会话的 `host-sim` 命令和切黑屏在发起的 Mac 上执行;这台 Mac 须与设备配对,跑 XCTest 还须有本机 runner。
@@ -25,17 +26,17 @@ iPhone、iPad 测试机由这个插件登记归属。每台设备是一个[并
25
26
  - 其他流程:`<callActivity calledElement="use-testbed">`,参数用 `coda:param`;项目流程先在自己的步骤构建、签名,把 App 路径传给它。
26
27
 
27
28
  **过程**:
28
- 1. 「找测试机」:先查本地登记;没有这个 id 就用 `coda-resources group-owner device:<id>` 读取归属实例声明的显示名、UDID 和说明。两边都没有则报「没有这台测试机」,列出本地可用设备,不排队。
29
+ 1. 「找测试机」:先查本地登记;没有这个 id 就用 `coda-resources group-owner device:<id>` 读取归属实例声明的显示名、UDID、说明和 `personal`。两边都没有则报「没有这台测试机」,列出本地可用设备,不排队。
29
30
  2. 「等测试机」:`hold-group` 为「占用测试机」这一段排进组 `device:<设备名>`(`capacity` 1、`borrow="no"`:每次使用都排队,做着上一次任务的会话再发起一次也排在后面),由引擎到归属实例排队,排到才往下走;之后到这一段结束都占着它。
30
31
  3. 有 `app` 就装上(`xcrun devicectl device install app`),再把任务交给调用的会话:要求是 `instructions`,加上设备名、UDID、借用方法和登记里的说明。
31
32
  做任务期间,这个会话(含子 Agent)用 `host-sim run --kind <设备名> -- <命令>` 操作设备:`host-sim` 经 `coda-run` 把命令交给能力 `run-on-device`,
32
33
  它用 `hold-group borrow="only"` 借这个组——只借给正在做这台设备任务的会话,别的情况立刻拒绝、不排队——借到后在会话的目录里、带会话的环境执行,
33
34
  附上 `HOST_SIM_LEASE=coda-run:<设备名>`(命令里再调用 `host-sim run --kind <设备名>` 直接执行,`host-sim check` 通过),退出码交回 `host-sim`。
34
35
  任务答复、撤回或运行被取消时,还在执行的这些命令随之停下([并发组](https://github.com/coda-console/coda/blob/main/docs/workflows.md#并发组)「借用跟着任务走」),下一步等它们停完才开始。
35
- 4. 「切黑屏」:任务答复、借用的命令停完后,在发起的 Mac 上用 `xcrun devicectl device process launch --terminate-existing` 在设备上打开[黑屏 App](#黑屏-app),限时 2 分钟;
36
+ 4. 个人设备(`personal: true`)在任务答复、出错、拒绝或取消后,等借用命令停完就直接离开「占用测试机」,放开设备,跳过切黑屏及其失败卡片。常驻测试机「切黑屏」:任务答复、借用的命令停完后,在发起的 Mac 上用 `xcrun devicectl device process launch --terminate-existing` 在设备上打开[黑屏 App](#黑屏-app),限时 2 分钟;
36
37
  设置「切黑屏命令」(`testbed.blackscreen`)里有这台设备的一行时,改为用 `sh` 执行那一行,设备的 UDID 在环境变量 `UDID` 里。
37
38
  装 App 或任务出错、任务被拒绝、运行被取消(流程等着 [`workflow.cancel`](https://github.com/coda-console/coda/blob/main/docs/workflows.md#执行与恢复))时,同样先切黑屏;切黑屏时被取消,照样切完。
38
- 5. 切不过去(命令失败或超时,如设备断开、锁屏或没装黑屏 App):给用户一张卡片「测试机切黑屏失败」,选「重试」或「我处理好了,放锁」;处理之前设备一直占着,不交给下一位。
39
+ 5. 常驻测试机切不过去(命令失败或超时,如设备断开、锁屏或没装黑屏 App):给用户一张卡片「测试机切黑屏失败」,选「重试」或「我处理好了,放锁」;处理之前设备一直占着,不交给下一位。
39
40
  卡片没有主按钮。
40
41
  6. 离开「占用测试机」这一段才放开组,排在后面的随即拿到。
41
42
 
@@ -52,7 +53,7 @@ iPhone、iPad 测试机由这个插件登记归属。每台设备是一个[并
52
53
  ## 黑屏 App
53
54
 
54
55
  插件的 `blackscreen/BlackScreen.swift`:纯黑全屏、隐藏状态栏与主屏幕指示条的 App,iPhone、iPad 通用(OLED 黑色像素不发光;LCD 的 iPad 在它前台时把亮度压到最低,离开时还原)。
55
- Bundle ID 是 `com.leavan.blackscreen`,切黑屏默认打开的就是它。每台测试机装一次;签名用的开发描述文件到期或换设备时重装:
56
+ Bundle ID 是 `com.leavan.blackscreen`,切黑屏默认打开的就是它。只有常驻测试机需要安装,每台装一次;个人设备无需安装。签名用的开发描述文件到期或换设备时重装:
56
57
 
57
58
  1. 在本仓库工作树的会话里,用持锁执行(`run-held`)运行 `sh plugins/testbed/blackscreen/build.sh <输出目录> <描述文件名>`,占一个构建名额;
58
59
  编译后用登录钥匙串里的 Apple Development 证书和这份开发描述文件签名(描述文件须是通配 App ID、包含目标设备,Team 取自描述文件),得到 `<输出目录>/BlackScreen.app`。它不碰设备。
package/main.mjs CHANGED
@@ -11,7 +11,7 @@ export async function activate(coda) {
11
11
  const list = devices(file, { missingIsEmpty: false });
12
12
  await coda.resources.declareGroups(list.map(device => ({
13
13
  name: `device:${device.id}`,
14
- attributes: { name: device.name ?? device.id, udid: device.udid, note: device.note ?? '' },
14
+ attributes: { name: device.name ?? device.id, udid: device.udid, note: device.note ?? '', personal: device.personal === true ? 'true' : 'false' },
15
15
  unique: ['udid'],
16
16
  })));
17
17
  }).catch(error => console.error(`测试机归属声明失败:${error.message}`));
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "id": "testbed",
4
4
  "displayName": "测试机",
5
5
  "description": "iPhone、iPad 测试机登记在哪个实例就归哪个实例,空间里同一设备只登记一次,重复占用报冲突。任何实例的会话都能用,在归属实例排队;安装、操作和切黑屏在发起的 Mac 执行,须与设备配对,XCTest 须有本机 runner。无需会话操作的流程可自行 hold-group device:<id>(capacity 1)独占。",
6
- "version": "1.2.0",
6
+ "version": "1.3.0",
7
7
  "categories": [
8
8
  "机器与资源"
9
9
  ],
@@ -1,4 +1,4 @@
1
- // Find a device's name, UDID and note for the step outputs. Registration determines its owning instance,
1
+ // Find a device's name, UDID, note and personal flag for the step outputs. Registration determines its owning instance,
2
2
  // whose declarations are published to the space. An id absent locally is resolved through group-owner;
3
3
  // if neither has it, report the missing device and the locally available ids.
4
4
  import { appendFileSync } from 'node:fs';
@@ -8,6 +8,6 @@ const device = process.env.DEVICE ?? '';
8
8
  let found;
9
9
  try { found = resolveDevice(device); } catch (error) { fail(error.message); }
10
10
  console.log(`测试机 ${device}:${found.name ?? device}(${found.udid})`);
11
- appendFileSync(process.env.GITHUB_OUTPUT, `name=${String(found.name ?? device).replace(/\n/g, ' ')}\nudid=${found.udid}\nnote<<CODA_NOTE\n${found.note ?? ''}\nCODA_NOTE\n`);
11
+ appendFileSync(process.env.GITHUB_OUTPUT, `name=${String(found.name ?? device).replace(/\n/g, ' ')}\nudid=${found.udid}\npersonal=${found.personal}\nnote<<CODA_NOTE\n${found.note ?? ''}\nCODA_NOTE\n`);
12
12
 
13
13
  function fail(message) { console.error(message); process.exit(1); }
@@ -5,8 +5,8 @@ import { devices } from './registry.mjs';
5
5
  export function resolveDevice(id) {
6
6
  const list = devices();
7
7
  const local = list.find(device => device.id === id);
8
- if (local) return local;
8
+ if (local) return { ...local, personal: local.personal === true };
9
9
  const owner = JSON.parse(execFileSync('coda-resources', ['group-owner', `device:${id}`], { encoding: 'utf8' }));
10
- if (owner) return { id, ...owner.attributes };
10
+ if (owner) return { id, ...owner.attributes, personal: owner.attributes.personal === 'true' };
11
11
  throw new Error(`没有这台测试机 ${id};本地可用:${list.map(device => device.id).join('、') || '无'}`);
12
12
  }
@@ -6,7 +6,9 @@ description: 测试机插件怎么用:会话或流程里用 use-testbed 排到
6
6
  # 测试机
7
7
 
8
8
  测试机(`iphone-testbed`、`ipad-testbed` 这类设备)需要会话操作时经能力 `use-testbed` 使用:它排进设备的并发组 `device:<设备名>`,
9
- 按需装 App,把任务交给发起的会话;答复、出错或运行被取消后,先等会话借设备还在跑的命令停下,再切黑屏、交出设备。
9
+ 按需装 App,把任务交给发起的会话;答复、出错、拒绝或运行被取消后,先等会话借设备还在跑的命令停下,常驻测试机再切黑屏、交出设备;个人设备直接交出,不切黑屏,也不出切黑屏失败卡片。
10
+
11
+ 设备登记每项可加 `"personal": true` 标记维护者的个人设备;缺省为常驻测试机。该字段随 `name`、`udid`、`note` 发布到空间,其他 Mac 经 `coda-resources group-owner` 也能读到。只有常驻测试机需要安装黑屏 App。
10
12
 
11
13
  登记在哪个实例就归哪个实例,`devices.json` 是归属的唯一事实来源。空间里同一设备只能登记一次,同名或相同 UDID 的重复登记会使占用失败并报冲突。任何实例的会话都能用,在归属实例排队。
12
14
  安装、会话的 host-sim 命令和切黑屏在发起的 Mac 上跑;这台 Mac 须与设备配对,跑 XCTest 还须有本机 runner。