@microi.net/cli 5.5.9 → 5.6.1

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.
Files changed (161) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/assets/build-meta.json +5 -5
  7. package/cordis.patch.yml +1 -1
  8. package/package.json +1 -1
  9. package/scripts/mcp-server.js +99 -99
  10. package/scripts/microi-cli.js +39 -36
  11. package/scripts/microi-skills.meta.json +341 -338
  12. package/skills/.microi-skills-version.json +2 -2
  13. package/skills/.progressive-disclosure-manifest.json +3566 -3566
  14. package/skills/README.md +4 -4
  15. package/skills/ai-engine/SKILL.md +45 -45
  16. package/skills/ai-platform-governance/SKILL.md +177 -177
  17. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  18. package/skills/app-store/SKILL.md +21 -4
  19. package/skills/business-blueprint/SKILL.md +85 -85
  20. package/skills/datasource-engine/SKILL.md +91 -91
  21. package/skills/dos-orm/SKILL.md +15 -0
  22. package/skills/dos-orm/references/api-reference.md +229 -229
  23. package/skills/email-engine/SKILL.md +80 -80
  24. package/skills/email-engine/references/v8-email.md +34 -34
  25. package/skills/job-engine/SKILL.md +154 -154
  26. package/skills/message-notification/SKILL.md +27 -1
  27. package/skills/message-notification/agents/openai.yaml +5 -5
  28. package/skills/message-notification/references/contracts.md +102 -102
  29. package/skills/microi-ai-app-auth.js +652 -652
  30. package/skills/microi-ai-application/SKILL.md +102 -102
  31. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  32. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  33. package/skills/microi-client-frontend/SKILL.md +47 -41
  34. package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +4 -4
  35. package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +30 -30
  36. package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +58 -58
  37. package/skills/microi-codex/SKILL.md +2 -0
  38. package/skills/microi-codex-installer/SKILL.md +216 -216
  39. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  40. package/skills/microi-datasource-mapping/SKILL.md +67 -67
  41. package/skills/microi-db-schema/SKILL.md +6 -6
  42. package/skills/microi-db-schema/references/core-tables.md +18 -18
  43. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  44. package/skills/microi-db-schema/references/schema-overview.md +11 -11
  45. package/skills/microi-db-schema/references/schema.md +46 -46
  46. package/skills/microi-db-schema/references/table-catalog.md +57 -57
  47. package/skills/microi-deployment/SKILL.md +18 -1
  48. package/skills/microi-deployment/references/deployment-matrix.md +101 -101
  49. package/skills/microi-docs-coverage/SKILL.md +10 -0
  50. package/skills/microi-docs-coverage/references/capability-map.md +3 -2
  51. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  52. package/skills/microi-form-engine/SKILL.md +27 -3
  53. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  54. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  55. package/skills/microi-form-layout/SKILL.md +13 -13
  56. package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -235
  57. package/skills/microi-frontend-sdk/SKILL.md +4 -4
  58. package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +175 -175
  59. package/skills/microi-left-right-layout/SKILL.md +141 -141
  60. package/skills/microi-microservice/SKILL.md +2 -1
  61. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  62. package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +213 -213
  63. package/skills/microi-solution-quotation/SKILL.md +78 -78
  64. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  65. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  66. package/skills/microi-sso/SKILL.md +6 -0
  67. package/skills/microi-sso/references/acceptance.md +49 -49
  68. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  69. package/skills/microi-sso/references/inbound.md +53 -53
  70. package/skills/microi-sso/references/outbound.md +39 -39
  71. package/skills/microi-system-delivery/SKILL.md +5 -4
  72. package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +7 -2
  73. package/skills/microi-ui/SKILL.md +7 -2
  74. package/skills/microi-uniapp-frontend/SKILL.md +134 -134
  75. package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +136 -136
  76. package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +124 -124
  77. package/skills/module-engine/SKILL.md +30 -14
  78. package/skills/ocr-engine/SKILL.md +2 -2
  79. package/skills/ocr-engine/agents/openai.yaml +1 -1
  80. package/skills/page-engine/SKILL.md +28 -28
  81. package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +126 -126
  82. package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +42 -42
  83. package/skills/performance-testing/SKILL.md +120 -120
  84. package/skills/playwright-e2e/SKILL.md +84 -84
  85. package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +68 -68
  86. package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +64 -64
  87. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +20 -20
  88. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +69 -69
  89. package/skills/print-engine/SKILL.md +11 -3
  90. package/skills/production-readonly-audit/SKILL.md +41 -41
  91. package/skills/report-engine/SKILL.md +71 -71
  92. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  93. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  94. package/skills/scripts/sync-embedded-skills.mjs +46 -0
  95. package/skills/scripts/validate-progressive-disclosure.mjs +8 -3
  96. package/skills/search-engine/SKILL.md +75 -75
  97. package/skills/spider-engine/SKILL.md +190 -190
  98. package/skills/system-observability/SKILL.md +169 -157
  99. package/skills/system-observability/references/memory-incident-triage.md +73 -73
  100. package/skills/translate-engine/SKILL.md +1 -1
  101. package/skills/ui-design/SKILL.md +3 -1
  102. package/skills/uniapp-mall-assets/SKILL.md +170 -170
  103. package/skills/unity-integration/SKILL.md +155 -155
  104. package/skills/unity-integration/agents/openai.yaml +4 -4
  105. package/skills/unity-integration/references/ai-app-delivery.md +103 -103
  106. package/skills/unity-integration/references/sdk-api.md +82 -82
  107. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  108. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  109. package/skills/v8-api-config/SKILL.md +4 -0
  110. package/skills/v8-cache-pattern/SKILL.md +306 -306
  111. package/skills/v8-crud-api/SKILL.md +29 -29
  112. package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +56 -56
  113. package/skills/v8-debugging/SKILL.md +143 -143
  114. package/skills/v8-explorer-tree/SKILL.md +64 -64
  115. package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +201 -201
  116. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +118 -118
  117. package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +32 -32
  118. package/skills/v8-file-upload/SKILL.md +5 -2
  119. package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +2 -2
  120. package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +138 -138
  121. package/skills/v8-formengine-http/SKILL.md +126 -108
  122. package/skills/v8-frontend-events/SKILL.md +22 -22
  123. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  124. package/skills/v8-frontend-events/references/bluetooth-print.md +247 -247
  125. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +50 -50
  126. package/skills/v8-http-integration/SKILL.md +106 -106
  127. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +31 -31
  128. package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +5 -5
  129. package/skills/v8-image-processing/SKILL.md +190 -190
  130. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  131. package/skills/v8-image-processing/references/api-reference.md +623 -623
  132. package/skills/v8-menu-buttons/SKILL.md +103 -103
  133. package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +145 -145
  134. package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +228 -228
  135. package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +43 -43
  136. package/skills/v8-mongodb/SKILL.md +65 -65
  137. package/skills/v8-mq-mqtt/SKILL.md +175 -175
  138. package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
  139. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +137 -137
  140. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  141. package/skills/v8-saas-multi-tenant/SKILL.md +223 -223
  142. package/skills/v8-security/SKILL.md +1 -1
  143. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +122 -122
  144. package/skills/v8-sql-query/SKILL.md +7 -5
  145. package/skills/v8-table-event/SKILL.md +9 -2
  146. package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +1 -1
  147. package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +15 -15
  148. package/skills/v8-tcp-integration/SKILL.md +1 -1
  149. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  150. package/skills/v8-template-engine/SKILL.md +59 -59
  151. package/skills/v8-utilities/SKILL.md +92 -92
  152. package/skills/v8-utilities/references/client-api-index.md +143 -143
  153. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  154. package/skills/v8-utilities/references/server-api-index.md +74 -74
  155. package/skills/v8-workflow/SKILL.md +13 -4
  156. package/skills/vision-engine/SKILL.md +19 -19
  157. package/skills/vision-engine/references/architecture-and-acceptance.md +34 -34
  158. package/skills/workspace-conventions/SKILL.md +35 -12
  159. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +2 -2
  160. package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +179 -179
  161. package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +21 -21
@@ -3,52 +3,52 @@
3
3
  > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
4
 
5
5
  <!-- microi-progressive:chunk id=playwright-e2e-008 sha256=353b1e7ec1d3e93d1fac92c8e9d6e520bf77b628a4141524a09404814f7affb7 -->
6
- ## 全自动登录(免验证码,但不免密码)——必读
7
-
8
- E2E 自动化最容易卡在「登录页有图形验证码」。Microi 后端允许自动化跳过验证码,但账号和密码必须始终走真实校验;如果密码配置错了,登录接口必须返回账号或密码错误。源码见 `Microi.Server/Microi.net.Api/Controllers/SysUserController.cs`(`Login` 方法开头)。
9
-
10
- ### 方式 A:自动化登录参数(远端/本地通用)
11
-
12
- - 触发条件:请求体传 `_AutomationTestLogin=true` 或 `_SkipCaptchaForAutomation=true`,且当前租户 `sys_config.AutoTestSkipCaptcha=true`(中文 Label:允许自动化测试登录时绕开验证码;字段缺失时按默认开启兼容旧库)。
13
- - 效果:只跳过图形验证码,仍校验 `Account` / `Pwd`。
14
- - 适用:MCP 连接远端租户、CI、Playwright 截图验收、接口自动化登录。
15
- - 风险控制:如果远端环境不希望自动化免验证码,请在系统设置关闭【允许自动化测试登录时绕开验证码】。
16
-
17
- Playwright 登录助手(直接拿 Token,不走登录页):
18
-
19
- ```js
20
- // helpers/microi-login.js
21
- export async function automationLogin(page, {
22
- backend = process.env.BACKEND || 'https://localhost:61501',
23
- osClient = process.env.MICROI_OSCLIENT || 'iTdos',
24
- account = process.env.PW_TEST_ACCOUNT || 'admin',
25
- password = process.env.PW_TEST_PASSWORD || '',
26
- frontend = process.env.FRONTEND || 'http://localhost:61500',
27
- } = {}) {
28
- const resp = await page.request.post(`${backend}/api/SysUser/Login`, {
29
- headers: { OsClient: osClient },
30
- data: { Account: account, Pwd: password, OsClient: osClient, _AutomationTestLogin: true },
31
- ignoreHTTPSErrors: true,
32
- });
33
- const json = await resp.json();
34
- if (json.Code !== 1) throw new Error('automationLogin failed: ' + JSON.stringify(json).slice(0, 300));
35
- const token = json.Data?.Token || json.Token;
36
- const userId = json.Data?.Id || json.Id;
37
- await page.goto(frontend, { waitUntil: 'domcontentloaded' });
38
- await page.evaluate(({ t, u, oc, account }) => {
39
- localStorage.setItem('Token', t);
40
- localStorage.setItem('CurrentUser', JSON.stringify({ Id: u, Account: account }));
41
- localStorage.setItem('OsClient', oc);
42
- }, { t: token, u: userId, oc: osClient, account });
43
- return { token, userId };
44
- }
45
- ```
46
-
47
- ### 选型口诀
48
-
49
- - 容器、CI、本机接口自动化都使用 **`_AutomationTestLogin=true` + 真实密码**,直接 request 拿 Token。
50
- - 需要验证真实 UI 登录时,用同一真实账号密码填写页面;目标租户仍由 `sys_config.AutoTestSkipCaptcha` 决定是否允许自动化跳过验证码。
51
- - 失败时再退回 UI 兜底并保留原始错误;禁止新增 `DevLoginBypass`、Dev Key 或 `_DEV_BYPASS_`。
6
+ ## 全自动登录(免验证码,但不免密码)——必读
7
+
8
+ E2E 自动化最容易卡在「登录页有图形验证码」。Microi 后端允许自动化跳过验证码,但账号和密码必须始终走真实校验;如果密码配置错了,登录接口必须返回账号或密码错误。源码见 `Microi.Server/Microi.net.Api/Controllers/SysUserController.cs`(`Login` 方法开头)。
9
+
10
+ ### 方式 A:自动化登录参数(远端/本地通用)
11
+
12
+ - 触发条件:请求体传 `_AutomationTestLogin=true` 或 `_SkipCaptchaForAutomation=true`,且当前租户 `sys_config.AutoTestSkipCaptcha=true`(中文 Label:允许自动化测试登录时绕开验证码;字段缺失时按默认开启兼容旧库)。
13
+ - 效果:只跳过图形验证码,仍校验 `Account` / `Pwd`。
14
+ - 适用:MCP 连接远端租户、CI、Playwright 截图验收、接口自动化登录。
15
+ - 风险控制:如果远端环境不希望自动化免验证码,请在系统设置关闭【允许自动化测试登录时绕开验证码】。
16
+
17
+ Playwright 登录助手(直接拿 Token,不走登录页):
18
+
19
+ ```js
20
+ // helpers/microi-login.js
21
+ export async function automationLogin(page, {
22
+ backend = process.env.BACKEND || 'https://localhost:61501',
23
+ osClient = process.env.MICROI_OSCLIENT || 'iTdos',
24
+ account = process.env.PW_TEST_ACCOUNT || 'admin',
25
+ password = process.env.PW_TEST_PASSWORD || '',
26
+ frontend = process.env.FRONTEND || 'http://localhost:61500',
27
+ } = {}) {
28
+ const resp = await page.request.post(`${backend}/api/SysUser/Login`, {
29
+ headers: { OsClient: osClient },
30
+ data: { Account: account, Pwd: password, OsClient: osClient, _AutomationTestLogin: true },
31
+ ignoreHTTPSErrors: true,
32
+ });
33
+ const json = await resp.json();
34
+ if (json.Code !== 1) throw new Error('automationLogin failed: ' + JSON.stringify(json).slice(0, 300));
35
+ const token = json.Data?.Token || json.Token;
36
+ const userId = json.Data?.Id || json.Id;
37
+ await page.goto(frontend, { waitUntil: 'domcontentloaded' });
38
+ await page.evaluate(({ t, u, oc, account }) => {
39
+ localStorage.setItem('Token', t);
40
+ localStorage.setItem('CurrentUser', JSON.stringify({ Id: u, Account: account }));
41
+ localStorage.setItem('OsClient', oc);
42
+ }, { t: token, u: userId, oc: osClient, account });
43
+ return { token, userId };
44
+ }
45
+ ```
46
+
47
+ ### 选型口诀
48
+
49
+ - 容器、CI、本机接口自动化都使用 **`_AutomationTestLogin=true` + 真实密码**,直接 request 拿 Token。
50
+ - 需要验证真实 UI 登录时,用同一真实账号密码填写页面;目标租户仍由 `sys_config.AutoTestSkipCaptcha` 决定是否允许自动化跳过验证码。
51
+ - 失败时再退回 UI 兜底并保留原始错误;禁止新增 `DevLoginBypass`、Dev Key 或 `_DEV_BYPASS_`。
52
52
  - Token 可能在响应体 `Data.Token`,也可能在响应头 `Authorization`,两处都要兜底取。
53
53
  - 不要把 Token 明文写进最终报告/附件。
54
54
 
@@ -56,14 +56,14 @@ export async function automationLogin(page, {
56
56
 
57
57
  > 实测:**仅把 Token 写进 localStorage 并不能通过前端路由守卫**——页面会反复跳回 `/login`,且动态菜单路由(如 `/order`)在登录态建立前会报 `No match found`。Dev Key 头旁路(方式 A)只对 `page.request` 直连接口有效(见 `blueprint-e2e.spec.mjs`),SPA 仍需要一次真实 UI 登录会话。
58
58
  >
59
- > 在浏览器内做 E2E(点页面、拖拽、截图)时最稳的登录顺序:
60
- > 1. 跳到 `#/login`;
61
- > 2. 填从受保护测试进程变量或用户本轮提供的真实账号密码;
62
- > 3. 目标租户开启 `AutoTestSkipCaptcha` 时传自动化标记跳过验证码,否则按真实页面流程填写验证码;
63
- > 4. 点「登 录」,落到首页;
64
- > 5. 再 `location.hash = '#/<目标路由>'` 进入目标页。
59
+ > 在浏览器内做 E2E(点页面、拖拽、截图)时最稳的登录顺序:
60
+ > 1. 跳到 `#/login`;
61
+ > 2. 填从受保护测试进程变量或用户本轮提供的真实账号密码;
62
+ > 3. 目标租户开启 `AutoTestSkipCaptcha` 时传自动化标记跳过验证码,否则按真实页面流程填写验证码;
63
+ > 4. 点「登 录」,落到首页;
64
+ > 5. 再 `location.hash = '#/<目标路由>'` 进入目标页。
65
65
  >
66
- > 直连接口验收(不进页面)使用自动化标记拿 Token;历史 `_DEV_BYPASS_`、Dev Key 和 `DevLoginBypass` 均不得继续使用。
66
+ > 直连接口验收(不进页面)使用自动化标记拿 Token;历史 `_DEV_BYPASS_`、Dev Key 和 `DevLoginBypass` 均不得继续使用。
67
67
 
68
68
  <!-- /microi-progressive:chunk -->
69
69
  <!-- microi-progressive:chunk id=playwright-e2e-009 sha256=f864be2aee2ce2490d2a8e241e5852fbd0777590f771397fd3c7973017889ae7 -->
@@ -82,33 +82,33 @@ Pop-Location
82
82
  该入口会执行 `scripts/run-form-engine-freeze-trace.mjs`,完整流程如下:
83
83
 
84
84
  1. 自动配置 `Microi.Server/Microi.net.Api/Properties/launchSettings.json` 中指定 profile 的 `ASPNETCORE_ENVIRONMENT` 和 `DOTNET_ENVIRONMENT`。
85
- 2. 从 `PW_TEST_ACCOUNT` / `PW_TEST_PASSWORD` 读取真实测试凭据;脚本不得修改后端 `appsettings.*.json`。
86
- 3. 本地后端未启动时,自动进入 `Microi.Server/Microi.net.Api` 后执行 `dotnet run --launch-profile Microi.net.Api`。
85
+ 2. 从 `PW_TEST_ACCOUNT` / `PW_TEST_PASSWORD` 读取真实测试凭据;脚本不得修改后端 `appsettings.*.json`。
86
+ 3. 本地后端未启动时,自动进入 `Microi.Server/Microi.net.Api` 后执行 `dotnet run --launch-profile Microi.net.Api`。
87
87
  4. 启动 Playwright,打开指定前端页面,开启 `MicroiFormTrace`,采集 `window.__MICROI_FORM_TRACE__`、console、pageerror、当前 URL 和 Playwright trace。
88
88
  5. 页面卡住或断言失败时,先看最后一批 `[MicroiFormTrace #n]`,定位是停在 `runtime:*`、`diy-select:*`、`inform-v8-*`、`field-v8-*` 还是业务 console。
89
89
 
90
90
  常用环境变量:
91
91
 
92
92
  ```powershell
93
- $env:FRONTEND='http://localhost:61500'
94
- $env:BACKEND='https://localhost:61501'
93
+ $env:FRONTEND='http://localhost:61500'
94
+ $env:BACKEND='https://localhost:61501'
95
95
  $env:PW_BACKEND_ENV='iTdos'
96
96
  $env:PW_ASPNETCORE_ENVIRONMENT='iTdos'
97
- $env:PW_DOTNET_ENVIRONMENT='iTdos'
98
- $env:PW_APPSETTINGS_ENV='iTdos'
99
- $env:MICROI_OSCLIENT='iTdos'
100
- $env:PW_TEST_ACCOUNT='<从本地配置读取或手工覆盖>'
101
- $env:PW_TEST_PASSWORD='<从本地配置读取或手工覆盖>'
102
- $env:MICROI_FREEZE_PATH='/#/diy/diy-design/<TableId>?PageType='
103
- npm run test:form-freeze:auto
104
- ```
97
+ $env:PW_DOTNET_ENVIRONMENT='iTdos'
98
+ $env:PW_APPSETTINGS_ENV='iTdos'
99
+ $env:MICROI_OSCLIENT='iTdos'
100
+ $env:PW_TEST_ACCOUNT='<从本地配置读取或手工覆盖>'
101
+ $env:PW_TEST_PASSWORD='<从本地配置读取或手工覆盖>'
102
+ $env:MICROI_FREEZE_PATH='/#/diy/diy-design/<TableId>?PageType='
103
+ npm run test:form-freeze:auto
104
+ ```
105
105
 
106
106
  可选开关:
107
107
 
108
108
  - `PW_START_BACKEND=0`:不自动启动后端,只跑测试。
109
109
  - `PW_CONFIG_BACKEND=0`:不修改 `launchSettings.json`。
110
- - `PW_BACKEND_PROFILE=Microi.net.Api`:指定 launch profile。
111
- - `PW_TEST_ACCOUNT`、`PW_TEST_PASSWORD`:仅注入当前自动化进程,必须使用真实账号密码且不得写回文件。
110
+ - `PW_BACKEND_PROFILE=Microi.net.Api`:指定 launch profile。
111
+ - `PW_TEST_ACCOUNT`、`PW_TEST_PASSWORD`:仅注入当前自动化进程,必须使用真实账号密码且不得写回文件。
112
112
  - `PW_HEADED=0`:无头运行。
113
113
 
114
114
  诊断代码要遵守这些规则:
@@ -152,7 +152,7 @@ uni-app H5、移动商城、分享海报、首页改版这类任务不能只跑
152
152
 
153
153
  ```powershell
154
154
  Set-Location '<uniapp-project-root>' # 替换为项目实际路径
155
- $env:PW_API_BASE='https://localhost:61501' # 替换为项目后端地址
155
+ $env:PW_API_BASE='https://localhost:61501' # 替换为项目后端地址
156
156
  $env:PW_API_ENV='development'
157
157
  $env:PW_PORT='5192' # 替换为项目实际端口
158
158
  npm run build:h5:local
@@ -73,69 +73,69 @@ expect(lowContrast, '低对比度文字: ' + JSON.stringify(lowContrast.slice(0,
73
73
 
74
74
  <!-- /microi-progressive:chunk -->
75
75
  <!-- microi-progressive:chunk id=playwright-e2e-014 sha256=e5858cce6f743573d699ed7d2d07f958d15be96c28028a91163e841f6d3c016a -->
76
- ## 安装
77
-
78
- ```bash
79
- npm i -D @playwright/test
80
- npx playwright install chromium
81
- ```
82
-
83
- ### 浏览器选择强制顺序
84
-
85
- 做本地页面级 Playwright 验收时,不能因为 Playwright 官方 Chromium 缓存缺失或下载超时就结束任务。必须按下面顺序自动兜底:
86
-
87
- 1. 优先读取 `PW_CHROMIUM_EXECUTABLE` / `PW_BROWSER_EXECUTABLE`。
88
- 2. Windows 优先探测本机 Edge / Chrome:`C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe`、`C:/Program Files/Microsoft/Edge/Application/msedge.exe`、`C:/Users/Administrator/AppData/Local/Google/Chrome/Bin/chrome.exe`、`C:/Program Files/Google/Chrome/Application/chrome.exe`、`C:/Program Files (x86)/Google/Chrome/Application/chrome.exe`。
89
- 3. 再探测工作区本地缓存:`.cache/chromium/**/chrome.exe`、`.tmp/playwright-browsers/**/chrome.exe`。
90
- 4. 仍没有浏览器时,才从吾码 CDN 下载到 `.tmp/playwright-browsers/`,并设置 `PW_CHROMIUM_EXECUTABLE`。
91
- 5. 只有系统浏览器、本地缓存和 CDN 下载全部失败时,才允许报告“浏览器不可用”;报告中必须说明已经尝试过哪些路径。
92
-
93
- 手写一次性脚本必须把浏览器探测逻辑写在 `.tmp/*.mjs` 中,截图和报告也必须输出到 `.tmp/screenshots/` 或 `.tmp/reports/`,不要写到 `Microi.Client/`、`Microi.Server/` 等子项目目录。
94
-
95
- 国内或内网环境如果下载浏览器困难,可以使用本机 Edge:
96
-
97
- ```bash
98
- $env:PW_BROWSER_CHANNEL='msedge'
99
- npx playwright test
100
- ```
101
-
102
- 如果本机没有可用 Edge,或必须使用 Chromium,优先从吾码 CDN 下载浏览器压缩包,不要反复卡在 Playwright 官方下载通道:
103
-
104
- | 系统 | CDN 地址 |
105
- |------|----------|
106
- | Windows x64 | `https://static.itdos.com/openclaw/chromium/chrome-win64-137.0.7151.68.zip` |
107
- | macOS x64 | `https://static.itdos.com/openclaw/chromium/chrome-mac-x64-137.0.7151.68.zip` |
108
- | macOS arm64 | `https://static.itdos.com/openclaw/chromium/chrome-mac-arm64-137.0.7151.68.zip` |
109
-
110
- Windows 示例:
111
-
112
- ```powershell
113
- $browserRoot = Join-Path (Resolve-Path '.tmp').Path 'playwright-browsers'
114
- New-Item -ItemType Directory -Force -Path $browserRoot | Out-Null
115
- $zipPath = Join-Path $browserRoot 'chrome-win64-137.0.7151.68.zip'
116
- Invoke-WebRequest 'https://static.itdos.com/openclaw/chromium/chrome-win64-137.0.7151.68.zip' -OutFile $zipPath
117
- Expand-Archive $zipPath -DestinationPath $browserRoot -Force
118
- $env:PW_CHROMIUM_EXECUTABLE = (Resolve-Path (Join-Path $browserRoot 'chrome-win64/chrome.exe')).Path
119
- npx playwright test
120
- ```
121
-
122
- 手写 Playwright 脚本时读取 `PW_CHROMIUM_EXECUTABLE` / `PW_BROWSER_EXECUTABLE` 并传给 `chromium.launch({ executablePath })`;Playwright Test 配置中通过 `use.launchOptions.executablePath` 读取该环境变量。只有 CDN、系统浏览器和本地缓存都不可用时,才报告浏览器不可用。
123
-
76
+ ## 安装
77
+
78
+ ```bash
79
+ npm i -D @playwright/test
80
+ npx playwright install chromium
81
+ ```
82
+
83
+ ### 浏览器选择强制顺序
84
+
85
+ 做本地页面级 Playwright 验收时,不能因为 Playwright 官方 Chromium 缓存缺失或下载超时就结束任务。必须按下面顺序自动兜底:
86
+
87
+ 1. 优先读取 `PW_CHROMIUM_EXECUTABLE` / `PW_BROWSER_EXECUTABLE`。
88
+ 2. Windows 优先探测本机 Edge / Chrome:`C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe`、`C:/Program Files/Microsoft/Edge/Application/msedge.exe`、`C:/Users/Administrator/AppData/Local/Google/Chrome/Bin/chrome.exe`、`C:/Program Files/Google/Chrome/Application/chrome.exe`、`C:/Program Files (x86)/Google/Chrome/Application/chrome.exe`。
89
+ 3. 再探测工作区本地缓存:`.cache/chromium/**/chrome.exe`、`.tmp/playwright-browsers/**/chrome.exe`。
90
+ 4. 仍没有浏览器时,才从吾码 CDN 下载到 `.tmp/playwright-browsers/`,并设置 `PW_CHROMIUM_EXECUTABLE`。
91
+ 5. 只有系统浏览器、本地缓存和 CDN 下载全部失败时,才允许报告“浏览器不可用”;报告中必须说明已经尝试过哪些路径。
92
+
93
+ 手写一次性脚本必须把浏览器探测逻辑写在 `.tmp/*.mjs` 中,截图和报告也必须输出到 `.tmp/screenshots/` 或 `.tmp/reports/`,不要写到 `Microi.Client/`、`Microi.Server/` 等子项目目录。
94
+
95
+ 国内或内网环境如果下载浏览器困难,可以使用本机 Edge:
96
+
97
+ ```bash
98
+ $env:PW_BROWSER_CHANNEL='msedge'
99
+ npx playwright test
100
+ ```
101
+
102
+ 如果本机没有可用 Edge,或必须使用 Chromium,优先从吾码 CDN 下载浏览器压缩包,不要反复卡在 Playwright 官方下载通道:
103
+
104
+ | 系统 | CDN 地址 |
105
+ |------|----------|
106
+ | Windows x64 | `https://static.itdos.com/openclaw/chromium/chrome-win64-137.0.7151.68.zip` |
107
+ | macOS x64 | `https://static.itdos.com/openclaw/chromium/chrome-mac-x64-137.0.7151.68.zip` |
108
+ | macOS arm64 | `https://static.itdos.com/openclaw/chromium/chrome-mac-arm64-137.0.7151.68.zip` |
109
+
110
+ Windows 示例:
111
+
112
+ ```powershell
113
+ $browserRoot = Join-Path (Resolve-Path '.tmp').Path 'playwright-browsers'
114
+ New-Item -ItemType Directory -Force -Path $browserRoot | Out-Null
115
+ $zipPath = Join-Path $browserRoot 'chrome-win64-137.0.7151.68.zip'
116
+ Invoke-WebRequest 'https://static.itdos.com/openclaw/chromium/chrome-win64-137.0.7151.68.zip' -OutFile $zipPath
117
+ Expand-Archive $zipPath -DestinationPath $browserRoot -Force
118
+ $env:PW_CHROMIUM_EXECUTABLE = (Resolve-Path (Join-Path $browserRoot 'chrome-win64/chrome.exe')).Path
119
+ npx playwright test
120
+ ```
121
+
122
+ 手写 Playwright 脚本时读取 `PW_CHROMIUM_EXECUTABLE` / `PW_BROWSER_EXECUTABLE` 并传给 `chromium.launch({ executablePath })`;Playwright Test 配置中通过 `use.launchOptions.executablePath` 读取该环境变量。只有 CDN、系统浏览器和本地缓存都不可用时,才报告浏览器不可用。
123
+
124
124
  <!-- /microi-progressive:chunk -->
125
125
  <!-- microi-progressive:chunk id=playwright-e2e-015 sha256=b9adf7af700e045fd919bdfde321b10df89660561dd8ab476a12cba310855875 -->
126
- ## playwright.config.js 模板
127
-
128
- ```js
129
- import { defineConfig, devices } from '@playwright/test';
126
+ ## playwright.config.js 模板
127
+
128
+ ```js
129
+ import { defineConfig, devices } from '@playwright/test';
130
130
 
131
131
  const port = process.env.PW_PORT || '5180';
132
- const baseURL = process.env.PW_BASE_URL || `http://127.0.0.1:${port}`;
133
- const webServerUrl = process.env.PW_WEB_SERVER_URL || baseURL;
134
- const channel = process.env.PW_BROWSER_CHANNEL || undefined;
135
- const executablePath = process.env.PW_CHROMIUM_EXECUTABLE || process.env.PW_BROWSER_EXECUTABLE || undefined;
136
- const defaultCommand = process.env.PW_APP_TYPE === 'uniapp-h5'
137
- ? `npm run dev:h5 -- --host 0.0.0.0 --port ${port}`
138
- : `npm run dev -- --host 0.0.0.0 --port ${port}`;
132
+ const baseURL = process.env.PW_BASE_URL || `http://127.0.0.1:${port}`;
133
+ const webServerUrl = process.env.PW_WEB_SERVER_URL || baseURL;
134
+ const channel = process.env.PW_BROWSER_CHANNEL || undefined;
135
+ const executablePath = process.env.PW_CHROMIUM_EXECUTABLE || process.env.PW_BROWSER_EXECUTABLE || undefined;
136
+ const defaultCommand = process.env.PW_APP_TYPE === 'uniapp-h5'
137
+ ? `npm run dev:h5 -- --host 0.0.0.0 --port ${port}`
138
+ : `npm run dev -- --host 0.0.0.0 --port ${port}`;
139
139
 
140
140
  export default defineConfig({
141
141
  testDir: './tests/e2e',
@@ -145,11 +145,11 @@ export default defineConfig({
145
145
  retries: process.env.CI ? 2 : 0,
146
146
  workers: process.env.CI ? 1 : undefined,
147
147
  reporter: [['list'], ['html', { open: 'never', outputFolder: 'tests/e2e/report' }]],
148
- use: {
149
- baseURL,
150
- ...(executablePath ? { launchOptions: { executablePath } } : {}),
151
- trace: 'retain-on-failure',
152
- screenshot: 'only-on-failure',
148
+ use: {
149
+ baseURL,
150
+ ...(executablePath ? { launchOptions: { executablePath } } : {}),
151
+ trace: 'retain-on-failure',
152
+ screenshot: 'only-on-failure',
153
153
  video: 'retain-on-failure',
154
154
  extraHTTPHeaders: {
155
155
  OsClient: process.env.PW_OS_CLIENT || 'demo'
@@ -15,22 +15,22 @@ export const microiEnv = {
15
15
  loginEngine: process.env.PW_LOGIN_ENGINE || 'member_login'
16
16
  };
17
17
 
18
- // 仅用于需要在 URL 中显式携带租户的普通 GET;POST 不调用此函数。
19
- export function withOsClientQuery(url) {
20
- const sep = url.includes('?') ? '&' : '?';
21
- return `${url}${sep}OsClient=${encodeURIComponent(microiEnv.osClient)}`;
22
- }
23
-
24
- export async function callEngine(request, apiEngineKey, data = {}, token = '') {
25
- const res = await request.post(`${microiEnv.apiBase}/apiengine/${apiEngineKey}`, {
26
- headers: {
27
- 'Content-Type': 'application/json',
28
- apiengine: '1',
29
- osclient: microiEnv.osClient,
30
- ...(token ? { Token: token } : {})
31
- },
32
- data: { ...data, OsClient: data.OsClient || microiEnv.osClient }
33
- });
18
+ // 仅用于需要在 URL 中显式携带租户的普通 GET;POST 不调用此函数。
19
+ export function withOsClientQuery(url) {
20
+ const sep = url.includes('?') ? '&' : '?';
21
+ return `${url}${sep}OsClient=${encodeURIComponent(microiEnv.osClient)}`;
22
+ }
23
+
24
+ export async function callEngine(request, apiEngineKey, data = {}, token = '') {
25
+ const res = await request.post(`${microiEnv.apiBase}/apiengine/${apiEngineKey}`, {
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ apiengine: '1',
29
+ osclient: microiEnv.osClient,
30
+ ...(token ? { Token: token } : {})
31
+ },
32
+ data: { ...data, OsClient: data.OsClient || microiEnv.osClient }
33
+ });
34
34
  expect(res.ok(), `${apiEngineKey} HTTP status`).toBeTruthy();
35
35
  const json = await res.json();
36
36
  return json;
@@ -156,7 +156,7 @@ tests/e2e/
156
156
  3. 写操作必须做“前置清理 → 执行动作 → 后端查询确认 → 用例清理”。
157
157
  4. UI 点击不能只断言 toast;必须用 `page.waitForResponse()` 捕捉对应接口,并验证响应体。
158
158
  5. 任何 FormEngine HTTP 路由必须使用 `/api/formengine/{action}-{table}`,不要生成 `/formengine/{table}/{action}`。
159
- 6. ApiEngine 使用稳定路径 `/apiengine/{ApiEngineKey}`,并通过唯一的 `osclient` Header 传租户;JSON/Form Body 可冗余携带 `OsClient`。普通 POST/PUT/PATCH/DELETE 禁止追加 `--OsClient--...--`。仅第三方回调或无法设置 Header/Form/Query 的 GET/HEAD 才允许特殊路径。
159
+ 6. ApiEngine 使用稳定路径 `/apiengine/{ApiEngineKey}`,并通过唯一的 `osclient` Header 传租户;JSON/Form Body 可冗余携带 `OsClient`。普通 POST/PUT/PATCH/DELETE 禁止追加 `--OsClient--...--`。仅第三方回调或无法设置 Header/Form/Query 的 GET/HEAD 才允许特殊路径。
160
160
  7. 页面级测试必须把 `请求失败`、`网络错误`、`null`、`待开发`、`开发中` 当成失败信号;这些文案如果出现在页面或 toast 中,说明功能未交付或接口契约错误。
161
161
  8. 新增、保存、结算、转赠、确认、上传等按钮不能只弹“成功/待开发”toast;必须调用真实业务接口,并通过后端查询验证状态变化。
162
162
  9. 源码守卫可作为兜底:递归扫描 `src/**/*.vue`、`src/**/*.js`,禁止残留 `待开发|开发中` 占位文案。
@@ -164,8 +164,8 @@ tests/e2e/
164
164
  11. 每个移动端项目都要有“接口清单驱动”的契约测试:静态扫描或维护清单覆盖所有 `callEngine('xxx')`,逐个 HTTP 调用并断言不是 404、不是空响应、不是字符串 `null`,且必须是标准 DosResult。
165
165
  12. 写业务闭环时必须准备可重复测试数据并清理:例如抢购要“创建测试挂单 → 调用抢购 → 验证订单 → 删除测试订单/挂单/提货卡”,购物车要“加入购物车 → 结算 → 验证订单/购物车状态 → 删除测试订单”。
166
166
  13. 图片断言必须检查真实渲染,而不是只检查元素可见。uni-app H5 的 `<image>` 会渲染成 `UNI-IMAGE`,真实图片在内部 `div.style.backgroundImage` 或子 `img` 上;不要把宿主元素的 CSS 渐变背景算作加载成功。
167
- 14. 网络守卫要记录 `requestfailed`,特别是图片资源的 `net::ERR_BLOCKED_BY_ORB` / CORS / 404;首页 banner、商品主图、头像等公开图片必须实际加载。
168
- 15. 页面出现浏览器原生 `alert`、`confirm` 或 `prompt` 直接判失败;测试应监听 `page.on('dialog')` 记录类型和文案后关闭对话框,再要求业务改用吾码 Tips、Element Plus 或品牌化可访问弹层。长弹窗需分别滚动到顶部、中部、底部触发反馈,并断言提示/确认层的 bounding box 中心接近当前 viewport 中心且未被遮挡。
167
+ 14. 网络守卫要记录 `requestfailed`,特别是图片资源的 `net::ERR_BLOCKED_BY_ORB` / CORS / 404;首页 banner、商品主图、头像等公开图片必须实际加载。
168
+ 15. 页面出现浏览器原生 `alert`、`confirm` 或 `prompt` 直接判失败;测试应监听 `page.on('dialog')` 记录类型和文案后关闭对话框,再要求业务改用吾码 Tips、Element Plus 或品牌化可访问弹层。长弹窗需分别滚动到顶部、中部、底部触发反馈,并断言提示/确认层的 bounding box 中心接近当前 viewport 中心且未被遮挡。
169
169
 
170
170
  移动商城/会员 H5 额外规则:
171
171
 
@@ -190,7 +190,7 @@ tests/e2e/
190
190
  - `microi_get_playwright_context`:获取可测菜单 URL、接口引擎、匿名配置。
191
191
  - `microi_plan_playwright_e2e`:生成推荐的测试文件、环境变量和冒烟路径。
192
192
  - `microi_run_engine`:调试单个接口引擎,不替代浏览器 E2E。
193
- - `microi_set_engine_anonymous`:登录、注册、公开首页接口需要匿名访问时使用;设置后仍要验证 HTTP `/apiengine/{key}` + `osclient` Header 返回标准 DosResult。
193
+ - `microi_set_engine_anonymous`:登录、注册、公开首页接口需要匿名访问时使用;设置后仍要验证 HTTP `/apiengine/{key}` + `osclient` Header 返回标准 DosResult。
194
194
 
195
195
  <!-- /microi-progressive:chunk -->
196
196
  <!-- microi-progressive:chunk id=playwright-e2e-022 sha256=d05000c40920ae53e9ce758385e2b7ad62a79d7d974491e241e6da8d698c9232 -->
@@ -29,7 +29,7 @@ jobs:
29
29
 
30
30
  <!-- /microi-progressive:chunk -->
31
31
  <!-- microi-progressive:chunk id=playwright-e2e-024 sha256=b939c97eef28fab71caa54d134ea524d7ed7c8381deec09e38a7d694b15f53c0 -->
32
- ## 常见问题
32
+ ## 常见问题
33
33
 
34
34
  | 现象 | 原因 | 处理 |
35
35
  |---|---|---|
@@ -41,75 +41,75 @@ jobs:
41
41
  | 保存接口代码后 MCP 自己报 `string does not contain DosIsNullOrWhiteSpace` | 后端 C# 在动态对象路径上调用扩展方法 | 改用 `string.IsNullOrWhiteSpace`,不要在 MCP 路径新增 `DosIsNullOrWhiteSpace` 调用 |
42
42
  | MCP `run_engine` 里 `V8.Db` 为 null | MCP 执行上下文没有注入直连 Db | 维护/测试引擎优先使用 `V8.FormEngine.GetTableData/UptFormData` |
43
43
  | H5 静态资源 404 | `vite.config.js` 未配置相对路径 | uni-app H5 设置 `base: './'` |
44
- | 本地浏览器下载失败 | Playwright 下载 Chromium 受阻 | 先用 `PW_BROWSER_CHANNEL=msedge`,没有 Edge 时从吾码 CDN 下载 Chromium 并设置 `PW_CHROMIUM_EXECUTABLE` |
45
- | 用例偶发失败 | HMR 或网络请求未稳定 | CI 使用静态构建,断言明确等待关键元素 |
46
- | 页面有横向滚动 | 组件撑出视口 | 对根容器和列表容器加 `max-width:100vw; overflow-x:hidden` |
47
-
48
- ### 多服务器、多租户并行上下文模板
49
-
50
- `incognito` 的本质是独立存储上下文。Playwright 的 `browser.newContext()` 每次都会创建隔离上下文,
51
- 比在同一个浏览器 context 中开多个 Page 更可靠:
52
-
53
- ```js
54
- function buildLocalMicroiUrl({ apiBase, osClient, route = '/' }) {
55
- return `http://localhost:61500/?OsClient=${encodeURIComponent(osClient)}`
56
- + `&ApiBase=${encodeURIComponent(apiBase)}#${route}`;
57
- }
58
-
59
- const targetA = await browser.newContext();
60
- const pageA = await targetA.newPage();
61
- await pageA.goto(buildLocalMicroiUrl({ apiBase: apiA, osClient: tenantA, route: '/home' }));
62
- await pageA.waitForFunction(() => Boolean(window.__MICROI_RUNTIME_ENDPOINT__));
63
- await expect.poll(() => pageA.evaluate(() => window.__MICROI_RUNTIME_ENDPOINT__)).toMatchObject({
64
- apiBase: apiA,
65
- osClient: tenantA,
66
- requiresIsolatedContextForParallelTenants: true
67
- });
68
-
69
- const targetB = await browser.newContext();
70
- // targetB 重复同样流程;禁止复用 targetA.newPage() 测 tenantB。
71
- ```
72
-
73
- 若给定的是线上吾码页面,先在第三个一次性 context 中读取
74
- `window.__MICROI_RUNTIME_ENDPOINT__`。旧版回退脚本只读取公开运行目标,不读取或输出 Token:
75
-
76
- ```js
77
- const endpoint = await page.evaluate(() => {
78
- const current = window.__MICROI_RUNTIME_ENDPOINT__;
79
- if (current?.apiBase && current?.osClient) return current;
80
- const stored = JSON.parse(localStorage.getItem('microi.net') || '{}');
81
- return {
82
- apiBase: window.ApiBase || stored.ApiBase || location.origin,
83
- osClient: new URLSearchParams(location.search).get('OsClient') || window.OsClient || stored.OsClient || ''
84
- };
85
- });
86
- ```
87
-
88
- OsClient 仍为空时调用目标站点域名租户解析接口或从成功请求确认。手工 Chrome 的第二个不同租户
89
- 至少使用无痕窗口,但同一 Chrome 无痕会话的多个窗口可能继续共享存储;三个以上目标使用独立
90
- Profile 或独立 `--user-data-dir`。
91
-
92
- <!-- /microi-progressive:chunk -->
44
+ | 本地浏览器下载失败 | Playwright 下载 Chromium 受阻 | 先用 `PW_BROWSER_CHANNEL=msedge`,没有 Edge 时从吾码 CDN 下载 Chromium 并设置 `PW_CHROMIUM_EXECUTABLE` |
45
+ | 用例偶发失败 | HMR 或网络请求未稳定 | CI 使用静态构建,断言明确等待关键元素 |
46
+ | 页面有横向滚动 | 组件撑出视口 | 对根容器和列表容器加 `max-width:100vw; overflow-x:hidden` |
47
+
48
+ ### 多服务器、多租户并行上下文模板
49
+
50
+ `incognito` 的本质是独立存储上下文。Playwright 的 `browser.newContext()` 每次都会创建隔离上下文,
51
+ 比在同一个浏览器 context 中开多个 Page 更可靠:
52
+
53
+ ```js
54
+ function buildLocalMicroiUrl({ apiBase, osClient, route = '/' }) {
55
+ return `http://localhost:61500/?OsClient=${encodeURIComponent(osClient)}`
56
+ + `&ApiBase=${encodeURIComponent(apiBase)}#${route}`;
57
+ }
58
+
59
+ const targetA = await browser.newContext();
60
+ const pageA = await targetA.newPage();
61
+ await pageA.goto(buildLocalMicroiUrl({ apiBase: apiA, osClient: tenantA, route: '/home' }));
62
+ await pageA.waitForFunction(() => Boolean(window.__MICROI_RUNTIME_ENDPOINT__));
63
+ await expect.poll(() => pageA.evaluate(() => window.__MICROI_RUNTIME_ENDPOINT__)).toMatchObject({
64
+ apiBase: apiA,
65
+ osClient: tenantA,
66
+ requiresIsolatedContextForParallelTenants: true
67
+ });
68
+
69
+ const targetB = await browser.newContext();
70
+ // targetB 重复同样流程;禁止复用 targetA.newPage() 测 tenantB。
71
+ ```
72
+
73
+ 若给定的是线上吾码页面,先在第三个一次性 context 中读取
74
+ `window.__MICROI_RUNTIME_ENDPOINT__`。旧版回退脚本只读取公开运行目标,不读取或输出 Token:
75
+
76
+ ```js
77
+ const endpoint = await page.evaluate(() => {
78
+ const current = window.__MICROI_RUNTIME_ENDPOINT__;
79
+ if (current?.apiBase && current?.osClient) return current;
80
+ const stored = JSON.parse(localStorage.getItem('microi.net') || '{}');
81
+ return {
82
+ apiBase: window.ApiBase || stored.ApiBase || location.origin,
83
+ osClient: new URLSearchParams(location.search).get('OsClient') || window.OsClient || stored.OsClient || ''
84
+ };
85
+ });
86
+ ```
87
+
88
+ OsClient 仍为空时调用目标站点域名租户解析接口或从成功请求确认。手工 Chrome 的第二个不同租户
89
+ 至少使用无痕窗口,但同一 Chrome 无痕会话的多个窗口可能继续共享存储;三个以上目标使用独立
90
+ Profile 或独立 `--user-data-dir`。
91
+
92
+ <!-- /microi-progressive:chunk -->
93
93
  <!-- microi-progressive:chunk id=playwright-e2e-025 sha256=be071153d695081b7c20d34cb5e9563480e3f09d9cd0292f2ca4b0245438787b -->
94
- ## 前端微服务 E2E 必测点
95
-
96
- 测试 Vue3 MicroApp 微服务时,不能只验证 `/micro-app/{OsClient}/{appKey}/index.html` 或带 token 的临时 URL。必须先建立真实登录态,再访问用户实际使用的不带 token 菜单路由,例如 `/#/micro-app/{MsKey}/{RoutePath}`,并确认地址栏没有退回旧的 `micro-app-host` 长地址。
97
-
98
- 同一套微服务绑定多个菜单时,至少选择 3 个真实菜单往返切换 8 轮,逐轮断言主框架 route、当前菜单和子应用可见内容匹配,且没有 `element head is missing`、`Failed to fetch`、`ERR_TOO_MANY_REDIRECTS`、`app name conflict`、永久骨架屏或空白内容。不能只断言 `<micro-app>` 元素存在。
99
-
100
- 页签缓存验收必须覆盖单一所有者与清理边界:在一个缓存范围内修改表单输入、筛选、内部路由或滚动位置,切走再返回后状态应保持;连续打开 6 个菜单微服务实例,断言运行时最多保留 5 个,最久未使用的隐藏实例重入时正常冷启动;关闭当前/其它/全部 Tab 后对应实例消失,退出登录后全部实例清空。恢复页还要断言收到最新 Token/OsClient/权限/主题/视口上下文,且任一时刻只有一个可见活动微服务。若子应用有轮询或 WebSocket,记录 `appstate-change`,确认 `afterhidden` 暂停、`aftershow` 幂等恢复而没有重复连接。
101
-
102
- 如果页面内提供 Microi SDK 调用按钮,必须点击并断言返回 `Code=1`,同时确认没有 `登录身份已过期`、`1001`、`1002`。只看到标题文本不代表鉴权链路通过。
103
-
94
+ ## 前端微服务 E2E 必测点
95
+
96
+ 测试 Vue3 MicroApp 微服务时,不能只验证 `/micro-app/{OsClient}/{appKey}/index.html` 或带 token 的临时 URL。必须先建立真实登录态,再访问用户实际使用的不带 token 菜单路由,例如 `/#/micro-app/{MsKey}/{RoutePath}`,并确认地址栏没有退回旧的 `micro-app-host` 长地址。
97
+
98
+ 同一套微服务绑定多个菜单时,至少选择 3 个真实菜单往返切换 8 轮,逐轮断言主框架 route、当前菜单和子应用可见内容匹配,且没有 `element head is missing`、`Failed to fetch`、`ERR_TOO_MANY_REDIRECTS`、`app name conflict`、永久骨架屏或空白内容。不能只断言 `<micro-app>` 元素存在。
99
+
100
+ 页签缓存验收必须覆盖单一所有者与清理边界:在一个缓存范围内修改表单输入、筛选、内部路由或滚动位置,切走再返回后状态应保持;连续打开 6 个菜单微服务实例,断言运行时最多保留 5 个,最久未使用的隐藏实例重入时正常冷启动;关闭当前/其它/全部 Tab 后对应实例消失,退出登录后全部实例清空。恢复页还要断言收到最新 Token/OsClient/权限/主题/视口上下文,且任一时刻只有一个可见活动微服务。若子应用有轮询或 WebSocket,记录 `appstate-change`,确认 `afterhidden` 暂停、`aftershow` 幂等恢复而没有重复连接。
101
+
102
+ 如果页面内提供 Microi SDK 调用按钮,必须点击并断言返回 `Code=1`,同时确认没有 `登录身份已过期`、`1001`、`1002`。只看到标题文本不代表鉴权链路通过。
103
+
104
104
  <!-- /microi-progressive:chunk -->
105
105
  <!-- microi-progressive:chunk id=playwright-e2e-026 sha256=3c8a1e8bbe281064f30f406703230d8df213f1f1e1d124edb8f9fc8a1df782ea -->
106
- ## 动态模块按钮验收
107
-
108
- 修复或新增 `MoreBtns/FormBtns/PageBtns/PageTabs` 时,运行页看到按钮只证明“最终渲染结果”,不能证明入口确实可由模块引擎维护。至少同时保留三类证据:
109
-
110
- 1. 后端/MCP 回读目标 `sys_menu`,断言对应按钮稳定 `Id`、`Name`、位置字段、`V8CodeShow` 和 `V8Code` 均已保存,并且未覆盖其它客户按钮。
111
- 2. 使用真实管理员登录模块引擎设计器,打开目标模块,断言相应按钮编辑区能看到该按钮及代码。
112
- 3. 分别在实际列表/卡片或 PC/移动端运行态检查按钮显隐并点击到目标交互;源码回归还要确认通用模板和宽度计算中不存在按模块 Id、Url、表名写死的同名入口。
113
-
114
- 按钮通过 `V8.OpenDialog` 打开主前端定制组件时,还要断言组件已在全局注册、`ComponentName` 与注册名一致、`DataAppend` 能到达组件,并确认源码没有新增 `V8.Open<业务名>` 专用包装。按钮通过 `V8.OpenAppDialog` 打开微服务时,按 AppKey、RoutePath 和发布版本回读并验收。涉及新旧版本滚动发布时分别说明组件/应用资源尚未就绪和已就绪的预期,不得只在单一版本页面上验收。
106
+ ## 动态模块按钮验收
107
+
108
+ 修复或新增 `MoreBtns/FormBtns/PageBtns/PageTabs` 时,运行页看到按钮只证明“最终渲染结果”,不能证明入口确实可由模块引擎维护。至少同时保留三类证据:
109
+
110
+ 1. 后端/MCP 回读目标 `sys_menu`,断言对应按钮稳定 `Id`、`Name`、位置字段、`V8CodeShow` 和 `V8Code` 均已保存,并且未覆盖其它客户按钮。
111
+ 2. 使用真实管理员登录模块引擎设计器,打开目标模块,断言相应按钮编辑区能看到该按钮及代码。
112
+ 3. 分别在实际列表/卡片或 PC/移动端运行态检查按钮显隐并点击到目标交互;源码回归还要确认通用模板和宽度计算中不存在按模块 Id、Url、表名写死的同名入口。
113
+
114
+ 按钮通过 `V8.OpenDialog` 打开主前端定制组件时,还要断言组件已在全局注册、`ComponentName` 与注册名一致、`DataAppend` 能到达组件,并确认源码没有新增 `V8.Open<业务名>` 专用包装。按钮通过 `V8.OpenAppDialog` 打开微服务时,按 AppKey、RoutePath 和发布版本回读并验收。涉及新旧版本滚动发布时分别说明组件/应用资源尚未就绪和已就绪的预期,不得只在单一版本页面上验收。
115
115
  <!-- /microi-progressive:chunk -->
@@ -106,9 +106,17 @@ ZICOX CC4、BLE/SPP 或
106
106
  }
107
107
  ```
108
108
 
109
- ### table — 表格元素
110
-
111
- ```json
109
+ 文本 `text` 与长文本 `longText` 的 `options.formatter` 接收
110
+ `function(title, value, options, templateData)`;文本控件还可能传第五个 `target`。
111
+ 第一参数是标题,绑定数据在第二参数。不要直接传入 `function(value)` 编码器,否则会把
112
+ 标题当业务值打印。表格单元格的 `formatter2(title, field, row, index, options)` 是另一合同。
113
+ 金额应由后端精确计算后作为字符串输出,不能在打印页转换为 `Number` 再求和。
114
+ 业务文本经过 formatter 进入 HTML 时必须编码 `& < > " '`,并用含标签的值与大额金额
115
+ 调用真实函数签名做回归。模板保存成功只证明存储,仍须用实际数据预览分页、合计与长条款。
116
+
117
+ ### table — 表格元素
118
+
119
+ ```json
112
120
  {
113
121
  "options": {
114
122
  "left": 30, "top": 150, "height": 56, "width": 511.5,