@sun-asterisk/sungen 2.4.6 → 2.5.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 (206) hide show
  1. package/README.md +88 -7
  2. package/dist/cli/commands/add.d.ts.map +1 -1
  3. package/dist/cli/commands/add.js +109 -9
  4. package/dist/cli/commands/add.js.map +1 -1
  5. package/dist/cli/commands/figma.d.ts +11 -0
  6. package/dist/cli/commands/figma.d.ts.map +1 -0
  7. package/dist/cli/commands/figma.js +178 -0
  8. package/dist/cli/commands/figma.js.map +1 -0
  9. package/dist/cli/commands/generate.d.ts.map +1 -1
  10. package/dist/cli/commands/generate.js +2 -0
  11. package/dist/cli/commands/generate.js.map +1 -1
  12. package/dist/cli/index.js +4 -2
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/generators/gherkin-parser/index.d.ts +1 -0
  15. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  16. package/dist/generators/gherkin-parser/index.js +3 -0
  17. package/dist/generators/gherkin-parser/index.js.map +1 -1
  18. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +29 -1
  19. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  20. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +21 -1
  21. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  22. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js +11 -2
  23. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  24. package/dist/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  25. package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  26. package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  27. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  28. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  29. package/dist/generators/test-generator/code-generator.d.ts +2 -0
  30. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  31. package/dist/generators/test-generator/code-generator.js +109 -12
  32. package/dist/generators/test-generator/code-generator.js.map +1 -1
  33. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  34. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  35. package/dist/generators/test-generator/step-mapper.js +1 -1
  36. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  37. package/dist/generators/test-generator/template-engine.d.ts +29 -1
  38. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  39. package/dist/generators/test-generator/template-engine.js +11 -2
  40. package/dist/generators/test-generator/template-engine.js.map +1 -1
  41. package/dist/generators/test-generator/utils/data-resolver.d.ts +11 -2
  42. package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
  43. package/dist/generators/test-generator/utils/data-resolver.js +36 -25
  44. package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
  45. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +7 -0
  46. package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -0
  47. package/dist/generators/test-generator/utils/runtime-data-transformer.js +42 -0
  48. package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -0
  49. package/dist/generators/types.d.ts +1 -0
  50. package/dist/generators/types.d.ts.map +1 -1
  51. package/dist/generators/types.js.map +1 -1
  52. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  53. package/dist/orchestrator/ai-rules-updater.js +2 -0
  54. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  55. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts +33 -0
  56. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts.map +1 -0
  57. package/dist/orchestrator/figma/figma-scaffolder-helpers.js +135 -0
  58. package/dist/orchestrator/figma/figma-scaffolder-helpers.js.map +1 -0
  59. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts +25 -0
  60. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts.map +1 -0
  61. package/dist/orchestrator/figma/figma-scaffolder-types.js +7 -0
  62. package/dist/orchestrator/figma/figma-scaffolder-types.js.map +1 -0
  63. package/dist/orchestrator/figma/figma-scaffolder.d.ts +23 -0
  64. package/dist/orchestrator/figma/figma-scaffolder.d.ts.map +1 -0
  65. package/dist/orchestrator/figma/figma-scaffolder.js +212 -0
  66. package/dist/orchestrator/figma/figma-scaffolder.js.map +1 -0
  67. package/dist/orchestrator/figma/node-path-collapser.d.ts +16 -0
  68. package/dist/orchestrator/figma/node-path-collapser.d.ts.map +1 -0
  69. package/dist/orchestrator/figma/node-path-collapser.js +37 -0
  70. package/dist/orchestrator/figma/node-path-collapser.js.map +1 -0
  71. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +44 -0
  72. package/dist/orchestrator/figma/spec-figma-renderer.d.ts.map +1 -0
  73. package/dist/orchestrator/figma/spec-figma-renderer.js +45 -0
  74. package/dist/orchestrator/figma/spec-figma-renderer.js.map +1 -0
  75. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +23 -0
  76. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts.map +1 -0
  77. package/dist/orchestrator/figma/spec-figma-section-renderers.js +47 -0
  78. package/dist/orchestrator/figma/spec-figma-section-renderers.js.map +1 -0
  79. package/dist/orchestrator/project-initializer.d.ts +9 -0
  80. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  81. package/dist/orchestrator/project-initializer.js +74 -10
  82. package/dist/orchestrator/project-initializer.js.map +1 -1
  83. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  84. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  85. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  86. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +34 -2
  87. package/dist/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  88. package/dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  89. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
  90. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +93 -23
  91. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  92. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  93. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  94. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  95. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +34 -2
  96. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  97. package/dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  98. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  99. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
  100. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  101. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +105 -28
  102. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  103. package/dist/orchestrator/templates/specs-base.d.ts +12 -1
  104. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  105. package/dist/orchestrator/templates/specs-base.js +47 -5
  106. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  107. package/dist/orchestrator/templates/specs-base.ts +65 -7
  108. package/dist/orchestrator/templates/specs-test-data.d.ts +14 -0
  109. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -0
  110. package/dist/orchestrator/templates/specs-test-data.js +100 -0
  111. package/dist/orchestrator/templates/specs-test-data.js.map +1 -0
  112. package/dist/orchestrator/templates/specs-test-data.ts +66 -0
  113. package/dist/tools/figma/figma-auth.d.ts +36 -0
  114. package/dist/tools/figma/figma-auth.d.ts.map +1 -0
  115. package/dist/tools/figma/figma-auth.js +182 -0
  116. package/dist/tools/figma/figma-auth.js.map +1 -0
  117. package/dist/tools/figma/figma-cache.d.ts +45 -0
  118. package/dist/tools/figma/figma-cache.d.ts.map +1 -0
  119. package/dist/tools/figma/figma-cache.js +191 -0
  120. package/dist/tools/figma/figma-cache.js.map +1 -0
  121. package/dist/tools/figma/figma-client-types.d.ts +112 -0
  122. package/dist/tools/figma/figma-client-types.d.ts.map +1 -0
  123. package/dist/tools/figma/figma-client-types.js +7 -0
  124. package/dist/tools/figma/figma-client-types.js.map +1 -0
  125. package/dist/tools/figma/figma-errors.d.ts +49 -0
  126. package/dist/tools/figma/figma-errors.d.ts.map +1 -0
  127. package/dist/tools/figma/figma-errors.js +105 -0
  128. package/dist/tools/figma/figma-errors.js.map +1 -0
  129. package/dist/tools/figma/figma-image-downloader.d.ts +25 -0
  130. package/dist/tools/figma/figma-image-downloader.d.ts.map +1 -0
  131. package/dist/tools/figma/figma-image-downloader.js +128 -0
  132. package/dist/tools/figma/figma-image-downloader.js.map +1 -0
  133. package/dist/tools/figma/figma-node-filter.d.ts +26 -0
  134. package/dist/tools/figma/figma-node-filter.d.ts.map +1 -0
  135. package/dist/tools/figma/figma-node-filter.js +164 -0
  136. package/dist/tools/figma/figma-node-filter.js.map +1 -0
  137. package/dist/tools/figma/figma-rest-client.d.ts +24 -0
  138. package/dist/tools/figma/figma-rest-client.d.ts.map +1 -0
  139. package/dist/tools/figma/figma-rest-client.js +154 -0
  140. package/dist/tools/figma/figma-rest-client.js.map +1 -0
  141. package/dist/tools/figma/figma-url-parser.d.ts +18 -0
  142. package/dist/tools/figma/figma-url-parser.d.ts.map +1 -0
  143. package/dist/tools/figma/figma-url-parser.js +51 -0
  144. package/dist/tools/figma/figma-url-parser.js.map +1 -0
  145. package/dist/utils/exec-file-no-throw.d.ts +20 -0
  146. package/dist/utils/exec-file-no-throw.d.ts.map +1 -0
  147. package/dist/utils/exec-file-no-throw.js +36 -0
  148. package/dist/utils/exec-file-no-throw.js.map +1 -0
  149. package/package.json +1 -1
  150. package/src/cli/commands/add.ts +80 -9
  151. package/src/cli/commands/figma.ts +162 -0
  152. package/src/cli/commands/generate.ts +2 -0
  153. package/src/cli/index.ts +4 -2
  154. package/src/generators/gherkin-parser/index.ts +4 -0
  155. package/src/generators/test-generator/adapters/adapter-interface.ts +12 -1
  156. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +14 -2
  157. package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +8 -0
  158. package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +8 -0
  159. package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +8 -0
  160. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
  161. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +24 -0
  162. package/src/generators/test-generator/code-generator.ts +122 -13
  163. package/src/generators/test-generator/step-mapper.ts +2 -2
  164. package/src/generators/test-generator/template-engine.ts +28 -2
  165. package/src/generators/test-generator/utils/data-resolver.ts +45 -27
  166. package/src/generators/test-generator/utils/runtime-data-transformer.ts +51 -0
  167. package/src/generators/types.ts +1 -0
  168. package/src/orchestrator/ai-rules-updater.ts +2 -0
  169. package/src/orchestrator/figma/figma-scaffolder-helpers.ts +126 -0
  170. package/src/orchestrator/figma/figma-scaffolder-types.ts +26 -0
  171. package/src/orchestrator/figma/figma-scaffolder.ts +209 -0
  172. package/src/orchestrator/figma/node-path-collapser.ts +38 -0
  173. package/src/orchestrator/figma/spec-figma-renderer.ts +80 -0
  174. package/src/orchestrator/figma/spec-figma-section-renderers.ts +46 -0
  175. package/src/orchestrator/project-initializer.ts +84 -10
  176. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  177. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  178. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  179. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +34 -2
  180. package/src/orchestrator/templates/ai-instructions/claude-config.md +12 -2
  181. package/src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  182. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +66 -13
  183. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +93 -23
  184. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  185. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  186. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  187. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  188. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +34 -2
  189. package/src/orchestrator/templates/ai-instructions/copilot-config.md +12 -2
  190. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  191. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  192. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +86 -13
  193. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  194. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +105 -28
  195. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  196. package/src/orchestrator/templates/specs-base.ts +65 -7
  197. package/src/orchestrator/templates/specs-test-data.ts +66 -0
  198. package/src/tools/figma/figma-auth.ts +161 -0
  199. package/src/tools/figma/figma-cache.ts +184 -0
  200. package/src/tools/figma/figma-client-types.ts +125 -0
  201. package/src/tools/figma/figma-errors.ts +127 -0
  202. package/src/tools/figma/figma-image-downloader.ts +112 -0
  203. package/src/tools/figma/figma-node-filter.ts +198 -0
  204. package/src/tools/figma/figma-rest-client.ts +183 -0
  205. package/src/tools/figma/figma-url-parser.ts +55 -0
  206. package/src/utils/exec-file-no-throw.ts +45 -0
@@ -151,6 +151,14 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
151
151
  | `@no-auth` | Disable inherited auth |
152
152
  | `@steps:name` | Define reusable step block (base scenario) |
153
153
  | `@extend:name` | Prepend Given→When from @steps block (skip Then) |
154
+ | `@cleanup:overlay` | Auto-cleanup: dismiss dialogs/overlays after each test (base.ts fixture) |
155
+ | `@cleanup:forms` | Auto-cleanup: clear form fields after each test (base.ts fixture) |
156
+ | `@cleanup:scroll` | Auto-cleanup: scroll to top after each test (base.ts fixture) |
157
+ | `@cleanup:storage` | Auto-cleanup: clear sessionStorage after each test (base.ts fixture) |
158
+ | `@screenshot:on-failure` | Auto-capture screenshot when test fails (base.ts fixture) |
159
+ | `@beforeAll` | Hook: runs once before all tests → `test.beforeAll()` |
160
+ | `@afterEach` | Hook: runs after each test → `test.afterEach()` (custom cleanup) |
161
+ | `@afterAll` | Hook: runs once after all tests → `test.afterAll()` |
154
162
 
155
163
  ### @extend behavior
156
164
 
@@ -175,30 +183,28 @@ Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `colu
175
183
  | Missing `is` for state | `with {{text}} hidden` | `with {{text}} is hidden` |
176
184
  | State as value | `with {{disabled}}` | `is disabled` |
177
185
  | Missing target type | `fill [email] with {{v}}` | `fill [email] field with {{v}}` |
178
- | Using Background | `Background: Given User is on...` | Use `@steps` + `@extend` instead |
186
+ | Background with scope | `Background: ... And User is on [X] dialog` | Use `@steps` + `@extend` for scope-dependent flows |
179
187
  | `is on` after When | `When ... And User is on [X] dialog` | `And User see [X] dialog` or separate Given |
180
188
 
181
189
  ## Background vs @steps/@extend
182
190
 
183
- **Do NOT use `Background:` block.** Use `@steps` + `@extend` instead.
191
+ Both `Background` and `@steps`/`@extend` are valid — they serve different purposes.
184
192
 
185
- **Why:**
186
- - `Background` runs before EVERY scenario but cannot set dialog/frame scope correctly
187
- - `Background` with `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When)
188
- - `@steps` + `@extend` gives explicit control: base steps run Given→When, extending scenario starts with `Given User is on [X] dialog` (correct scope setup)
193
+ | Pattern | Use when | Generates |
194
+ |---|---|---|
195
+ | `Background` | Simple shared setup (navigate to page) | `test.beforeEach()` |
196
+ | `@steps`/`@extend` | Complex reusable flows with scope (dialog, frame) | Inline merged steps in `test()` |
189
197
 
190
- **Wrong:**
198
+ **Use `Background` for simple navigation:**
191
199
  ```gherkin
192
200
  Background:
193
- Given User is on [Kudos] page
194
- When User click [Open] button
195
- And User is on [Modal] dialog ← keyword mismatch
201
+ Given User is on [Dashboard] page
196
202
 
197
- Scenario: VP-UI-001 Title visible
198
- Then User see [Title] heading
203
+ Scenario: View stats
204
+ Then User see [Revenue Chart] section
199
205
  ```
200
206
 
201
- **Correct:**
207
+ **Use `@steps`/`@extend` when scope matters (dialog, frame):**
202
208
  ```gherkin
203
209
  @steps:open_modal
204
210
  Scenario: Open modal
@@ -211,3 +217,70 @@ Scenario: VP-UI-001 Title visible
211
217
  Given User is on [Modal] dialog
212
218
  Then User see [Title] heading
213
219
  ```
220
+
221
+ **Avoid `Background` with scope-dependent steps** — `When` + `And User is on [X] dialog` creates keyword mismatch (`is on` = Given, not When). Use `@steps`/`@extend` instead.
222
+
223
+ ## Hooks & Cleanup
224
+
225
+ Two layers for test lifecycle management. Prefer `@cleanup:*` tags (Layer 1) — they work with base.ts automatically. Use hook scenarios (Layer 2) only for custom logic.
226
+
227
+ ### Layer 1: `@cleanup:*` tags (automatic via base.ts)
228
+
229
+ Feature-level tags that activate cleanup fixtures in base.ts. No Gherkin steps needed.
230
+
231
+ ```gherkin
232
+ @auth:admin
233
+ @cleanup:overlay
234
+ @cleanup:forms
235
+ Feature: User Management
236
+ Path: /users
237
+
238
+ Background:
239
+ Given User is on [User Management] page
240
+
241
+ Scenario: Create user shows form
242
+ When User click [Add User] button
243
+ Then User see [Create User] dialog
244
+ # After test: overlay auto-dismissed, forms auto-cleared by base.ts
245
+
246
+ Scenario: Search user by name
247
+ When User fill [Search] field with {{search_name}}
248
+ Then User see [User Row] row
249
+ # After test: search field auto-cleared by base.ts
250
+ ```
251
+
252
+ | Tag | What base.ts does after each test |
253
+ |---|---|
254
+ | `@cleanup:overlay` | Press Escape, click body, dismiss fixed overlays |
255
+ | `@cleanup:forms` | Clear all input/textarea fields, reset selects |
256
+ | `@cleanup:scroll` | Scroll to top of page |
257
+ | `@cleanup:storage` | Clear sessionStorage |
258
+
259
+ ### Layer 2: `@afterEach` scenario (custom cleanup)
260
+
261
+ Only when `@cleanup:*` tags aren't enough — feature-specific logic.
262
+
263
+ ```gherkin
264
+ @auth:admin
265
+ @cleanup:overlay
266
+ Feature: Dashboard
267
+ Path: /dashboard
268
+
269
+ Background:
270
+ Given User is on [Dashboard] page
271
+
272
+ @afterEach
273
+ Scenario: Reset dashboard filters
274
+ When User select [Date Filter] dropdown with {{default_period}}
275
+
276
+ Scenario: Filter by last week
277
+ When User select [Date Filter] dropdown with {{last_week}}
278
+ Then User see [Revenue Chart] section
279
+ ```
280
+
281
+ ### Layer 3: `@beforeAll` / `@afterAll` (optional)
282
+
283
+ For one-time setup/teardown. Low priority — most e2e tests don't need these.
284
+
285
+ **Rendering order in `.spec.ts`:**
286
+ `test.describe` → `test.use(storageState)` → `test.use(autoCleanup)` → `test.beforeAll` → `test.beforeEach` → `test.afterEach` → `test.afterAll` → `test()` blocks
@@ -52,6 +52,67 @@ If existing selectors already cover the feature file, **skip Phase 0** and go st
52
52
 
53
53
  ---
54
54
 
55
+ ## Phase 0 (Provisional): Figma-Only Selector Generation (no live page)
56
+
57
+ **Trigger**: Phase 0 decision tree (in `run-test` command) determined the live page is not reachable AND `requirements/spec_figma.md` exists.
58
+
59
+ **Goal**: produce a compilable `selectors.yaml` seeded from Figma node data so that `sungen generate` succeeds and the test suite can run. Every entry is provisional — it must be verified against a real DOM when the page becomes available.
60
+
61
+ ### Inputs
62
+
63
+ - `requirements/spec_figma.md` — specifically:
64
+ - `## Components` table (Name, Type, Role, Text, Variants columns)
65
+ - `## Text Inventory` list (node_id → label text pairs)
66
+
67
+ ### Steps
68
+
69
+ 1. **Parse references**: collect every `[Reference]` element from the `.feature` file (same as live Phase 0 step 2).
70
+ 2. **Match to Figma nodes**: for each `[Reference]`, find the best matching entry in `spec_figma.md` by comparing the reference name against component names and text labels (case-insensitive, partial match allowed).
71
+ 3. **Apply heuristic priority** (same order as live Phase 0 — use first applicable):
72
+
73
+ | Priority | Type | Figma Signal |
74
+ |---|---|---|
75
+ | 1 | `testid` | Dev annotation `data-testid=X` in component metadata |
76
+ | 2 | `role` + name | Component type is Button/Link + has text label |
77
+ | 3 | `placeholder` | Component type is Input/Field + has placeholder text |
78
+ | 4 | `label` | Component has associated label text (not placeholder) |
79
+ | 5 | `locator` (CSS) | No accessible name derivable from Figma data |
80
+ | 6 | `text` | Plain text node (not inside interactive component) |
81
+
82
+ See `sungen-figma-source` skill for the authoritative heuristics table mapping Figma signals to YAML entry types.
83
+
84
+ 4. **Write entries**: add each entry to `selectors.yaml`, prefixed with the required comment:
85
+ ```yaml
86
+ # @needs-live-verify source=figma node_id=<figma-node-id>
87
+ submit-button:
88
+ type: role
89
+ value: button
90
+ name: "Submit"
91
+ ```
92
+ - Key naming follows `sungen-selector-keys` conventions (lowercase, Unicode preserved, `--type` / `--N` suffixes).
93
+ - Copy text character-for-character from `## Text Inventory`. Never infer from the Gherkin label.
94
+ - Merge, don't overwrite — preserve the page selector and any user-authored entries.
95
+
96
+ 5. **Compile**: `sungen generate --screen <screen>` — must succeed before Phase 1.
97
+
98
+ ### Auto-fix on live page (subsequent run-test invocations)
99
+
100
+ When `run-test` is called again and the live page is now reachable:
101
+ - Phase 0 takes ONE `browser_snapshot`.
102
+ - For each entry tagged `# @needs-live-verify` in `selectors.yaml`:
103
+ - Compare the provisional selector against the actual DOM element found in the snapshot.
104
+ - If match → remove the comment line (entry promoted to verified).
105
+ - If mismatch → replace the YAML value with the snapshot-derived selector, then remove the comment (entry corrected and verified).
106
+ - Entries with no corresponding DOM element → log as unresolved, leave comment in place for manual inspection.
107
+
108
+ ### Pitfalls
109
+
110
+ - Inferring selector names from Gherkin labels instead of Figma `## Text Inventory` → mismatch on live verification.
111
+ - Omitting `# @needs-live-verify` comment → reviewer has no way to count unverified selectors.
112
+ - Overwriting user-authored selectors during merge → always check for existing keys before adding.
113
+
114
+ ---
115
+
55
116
  ## Phase 0.5: Auth Persistence (MCP alternative to `sungen makeauth`)
56
117
 
57
118
  Capture an authenticated session from the MCP browser into `specs/.auth/<role>.json` — the same path `sungen makeauth` writes to, which compiled tests already reference via `test.use({ storageState })` based on `@auth:<role>` tags. No `playwright.config.ts` edits needed. Run once per auth lifetime, not on every selector fix.
@@ -6,14 +6,25 @@ user-invocable: false
6
6
 
7
7
  ## Goal
8
8
 
9
- Generate **focused test cases per screen section** with **20+ scenarios per viewpoint**. Output `.feature` + `test-data.yaml` only — selectors are deferred to `/sungen:run-test`.
9
+ Generate **focused test cases per screen section** using a **tier-based approach** for faster results. Output `.feature` + `test-data.yaml` only — selectors are deferred to `/sungen:run-test`.
10
+
11
+ ### Tier System
12
+
13
+ | Tier | Priority | What to generate | When |
14
+ |---|---|---|---|
15
+ | **Tier 1** (default) | `@critical` + `@high` | Happy paths, required validation, core business rules, security basics | First run of `create-test` |
16
+ | **Tier 2** (expand) | `@normal` + `@low` | UI presence, optional validation, edge cases, cosmetic checks | User runs `create-test` again with "Add viewpoints" mode |
17
+
18
+ **Round 1 (Tier 1)** targets **~10-15 scenarios per section** — enough to cover critical flows and catch real bugs. This is the default behavior.
19
+
20
+ **Round 2 (Tier 2)** expands to full coverage when the user explicitly chooses "Add viewpoints" or "Add new sections" update mode. Only then generate `@normal` + `@low` scenarios to fill coverage gaps.
10
21
 
11
22
  ## Update Mode
12
23
 
13
24
  When `.feature` already has scenarios, summarize and ask:
14
- 1. **Add new sections** — append, continue numbering
15
- 2. **Add viewpoints** — add to existing sections
16
- 3. **Replace all** — overwrite
25
+ 1. **Add new sections** — append new sections with Tier 2 (`@normal` + `@low`) scenarios, continue numbering
26
+ 2. **Add viewpoints** — expand existing sections with Tier 2 (`@normal` + `@low`) scenarios
27
+ 3. **Replace all** — overwrite with fresh Tier 1 (`@critical` + `@high`) generation
17
28
 
18
29
  For append: read highest `VP-<CAT>-<NNN>`, continue from next number. Never modify existing scenarios.
19
30
 
@@ -28,14 +39,28 @@ Requirements improve every viewpoint: exact error messages for VAL, business rul
28
39
 
29
40
  If also exploring live page: verify spec vs actual, flag mismatches, capture exact text.
30
41
 
42
+ ### Figma supplement (`spec_figma.md`)
43
+
44
+ When `requirements/spec_figma.md` is present alongside `spec.md`, treat it as a **secondary input** with these rules:
45
+
46
+ - **Never override `spec.md`**: `spec.md` is authoritative for all business rules, field constraints, and behavior. `spec_figma.md` only supplements with visual/text data that `spec.md` may lack.
47
+ - **`## Text Inventory` → literal strings**: use text label values from this section verbatim in `test-data.yaml` (button labels, input placeholders, error messages shown in Figma). Do not paraphrase or invent alternatives.
48
+ - **`## Interaction States` → state coverage checkpoints**: use the listed variants (e.g., empty, loading, error, success) as a checklist for state-coverage scenarios. Only generate scenarios for states that are either (a) confirmed in both `spec.md` and `spec_figma.md`, or (b) explicitly documented in one source without contradiction from the other.
49
+ - **Flag disagreements**: if a field name, label, or behavior in `spec_figma.md` contradicts `spec.md`, insert an HTML comment at the top of the `.feature` file:
50
+ ```gherkin
51
+ <!-- FIGMA-SPEC CONFLICT: <brief description> — using spec.md value -->
52
+ ```
53
+ Then proceed using the `spec.md` value.
54
+
31
55
  ## Screen Input Sources
32
56
 
33
- **Recommended** (in priority order):
34
- 1. **Figma designs** use Figma MCP to read design context, screenshots, and component metadata
35
- 2. **UI images** screenshots or mockups in `qa/screens/<screen>/requirements/ui/`
36
- 3. **`spec.md`**written specification with sections, fields, validation rules
57
+ **Auto-detect** — the parent command (`create-test`) resolves visual sources before invoking this skill. By the time generation starts, the available sources are already determined:
58
+ - `spec.md`primary, always read if present
59
+ - `spec_figma.md` Figma supplement, read if present (PAT flow already completed)
60
+ - `ui/*.png`visual context, read if present
61
+ - `test-viewpoint.md` — edge cases and known issues, read if present
37
62
 
38
- **Optional**: **Live page scan** via Playwright MCP — useful to verify specs vs actual UI, capture real data (error messages, labels). If auth needed ask user to log in manually via MCP browser.
63
+ **IMPORTANT:** If `spec_figma.md` exists, do NOT call any `mcp__figma__*` tool. The PAT flow is complete just read the file.
39
64
 
40
65
  **Single screen focus**: one URL = one screen. Don't explore sibling paths. Modals on same page = part of this screen.
41
66
 
@@ -76,7 +101,9 @@ Apply `sungen-test-design-techniques` to spec-extracted conditions:
76
101
 
77
102
  Use `sungen-viewpoint` skill for per-pattern checklists across 4 viewpoints: UI/UX, Data & Validate, Logic, Security.
78
103
 
79
- Add scenarios for generic UI coverage that spec didn't explicitly state (empty states, loading states, keyboard nav, hover effects). Skip viewpoints truly N/A.
104
+ **Tier-aware gap filling:**
105
+ - **Tier 1 (first run)**: only add `@critical` and `@high` items from the checklists — core security checks (VP-SEC), required field validation (VP-VAL), key state transitions (VP-LOGIC). Skip `@normal`/`@low` items like hover states, empty states, tooltips.
106
+ - **Tier 2 (expand run)**: add `@normal` + `@low` scenarios — UI presence, optional validation, edge cases, cosmetic checks, keyboard nav, hover effects.
80
107
 
81
108
  **Validation rule**: capture actual error messages from live page or spec.md. Use `User see {{error_var}}` — never assert just "is visible".
82
109
 
@@ -112,30 +139,70 @@ Given User is on [Screen] page
112
139
  And User wait for [Page Title] heading is visible
113
140
  ```
114
141
 
142
+ ## Cleanup & Hooks
143
+
144
+ ### Auto-assign `@cleanup:*` tags based on screen sections
145
+
146
+ After identifying screen sections, add appropriate `@cleanup:*` feature-level tags. These activate base.ts fixtures that auto-clean state between tests.
147
+
148
+ | Screen has | Add tag | Why |
149
+ |---|---|---|
150
+ | Modal / Dialog / Drawer | `@cleanup:overlay` | Dismiss leftover overlays between tests |
151
+ | Form & Inputs / Search / Filter | `@cleanup:forms` | Clear form fields, reset selects |
152
+ | Long scrollable content | `@cleanup:scroll` | Scroll to top for consistent assertions |
153
+ | Auth tokens / session data in tests | `@cleanup:storage` | Clear sessionStorage |
154
+ | CI/CD or debug-heavy screens | `@screenshot:on-failure` | Auto-capture screenshot on test failure |
155
+
156
+ **Always add `@cleanup:overlay`** if ANY section opens a dialog (Create/Add, Update/Edit, Delete confirmation). Most CRUD screens need it.
157
+
158
+ **Always add `@cleanup:forms`** if the screen has inline search, filter dropdowns, or editable forms that persist between tests.
159
+
160
+ ### When to add `@afterEach` hook scenario
161
+
162
+ Only when `@cleanup:*` tags aren't enough — feature-specific cleanup logic:
163
+ - Reset a dropdown filter to default value (not just clear)
164
+ - Navigate away from a sub-tab back to the main tab
165
+ - Close a specific sidebar panel
166
+
167
+ ```gherkin
168
+ @afterEach
169
+ Scenario: Reset filters to default
170
+ When User select [Status Filter] dropdown with {{default_status}}
171
+ ```
172
+
173
+ ### `@beforeAll` / `@afterAll` — optional, low priority
174
+
175
+ For one-time setup/teardown. Most screens don't need these.
176
+
115
177
  ## Output Format
116
178
 
117
179
  **Feature file** — `qa/screens/<screen>/features/<screen>.feature`
118
180
 
119
- **Never use `Background:`.** Use `@steps` + `@extend` for shared setup (see `sungen-gherkin-syntax` skill).
181
+ `Background` is valid for simple shared setup (navigate to page). Use `@steps`/`@extend` for complex flows with scope (dialog, frame).
120
182
 
121
183
  ```gherkin
122
184
  @auth:role
185
+ @cleanup:overlay
186
+ @cleanup:forms
123
187
  Feature: <Screen> Screen
124
188
 
189
+ Background:
190
+ Given User is on [Screen] page
191
+
125
192
  # Shared setup — NO priority tag on @steps
126
193
  @steps:open_form
127
194
  Scenario: Open form
128
- Given User is on [Screen] page
129
195
  When User click [Create] button
130
196
  Then User see [Form] dialog
131
197
 
132
- # --- Section: Create User Form ---
198
+ # --- Section: Create User Form (Tier 1: @critical + @high) ---
133
199
 
134
- @normal @extend:open_form
135
- Scenario: VP-UI-001 Form displays all fields with correct defaults
200
+ @critical @extend:open_form
201
+ Scenario: VP-LOGIC-001 Submit form with valid data creates record
136
202
  Given User is on [Form] dialog
137
- Then User see [Name] field
138
- And User see [Submit] button is disabled
203
+ When User fill [Name] field with {{valid_name}}
204
+ And User click [Submit] button
205
+ Then User see {{success_message}} message
139
206
 
140
207
  @high @extend:open_form
141
208
  Scenario: VP-VAL-001 Submit with all empty fields shows errors
@@ -143,11 +210,7 @@ Feature: <Screen> Screen
143
210
  When User click [Submit] button
144
211
  Then User see [Name error] message with {{name_required_error}}
145
212
 
146
- # --- Section: User Table ---
147
-
148
- @normal
149
- Scenario: VP-UI-010 Table displays all columns
150
- Then User see [Name] column in [Users] table
213
+ # --- Section: User Table (Tier 1: @critical + @high) ---
151
214
 
152
215
  @high
153
216
  Scenario: VP-VAL-010 Table displays correct data
@@ -155,13 +218,15 @@ Feature: <Screen> Screen
155
218
  | Name | Email |
156
219
  | {{name_1}} | {{email_1}} |
157
220
 
158
- @high
159
- Scenario: VP-VAL-011 Edit button targets correct row
160
- Given User see [Target] row in [Users] table with {{name_1}}
161
- When User click [Edit] button in [Users] table with {{name_1}}
162
- Then User see [Name] field with {{name_1}}
221
+ @critical
222
+ Scenario: VP-SEC-010 Unauthorized user cannot access page
223
+ Given User is not logged in
224
+ When User navigate to [Screen] page
225
+ Then User is on [Login] page
163
226
  ```
164
227
 
228
+ **Tier 2 (expand run)** adds `@normal` + `@low` scenarios like UI field presence, hover states, tooltips, empty states.
229
+
165
230
  ### When to use DataTable vs Row Scope
166
231
 
167
232
  | Pattern | Use when |
@@ -171,6 +236,18 @@ Feature: <Screen> Screen
171
236
 
172
237
  **Naming**: `VP-<CATEGORY>-<NNN>` prefix. Scenario name must use the **same element type** as the steps — e.g., if the step uses `dialog`, write "dialog opens" not "modal opens".
173
238
 
174
- **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section.
239
+ **Test data** — `qa/screens/<screen>/test-data/<screen>.yaml`, grouped by section. Data is loaded **at runtime** — keys become runtime lookups, not hardcoded strings. The same compiled test works across environments.
240
+
241
+ **Environment-specific data**: For values that differ per environment (credentials, URLs, test users), create `<screen>.<env>.yaml` alongside the base file. Users run `SUNGEN_ENV=staging npx playwright test` to merge overrides. Structure env YAML with the same keys, only including values that change:
242
+
243
+ ```yaml
244
+ # login.yaml (base)
245
+ valid_email: admin@dev.example.com
246
+ valid_password: DevPass123
247
+
248
+ # login.staging.yaml (override for staging)
249
+ valid_email: admin@staging.example.com
250
+ valid_password: StagingPass456
251
+ ```
175
252
 
176
253
  **Do NOT generate**: `selectors.yaml` (created during run-test), Playwright code (sungen compiles).
@@ -43,6 +43,8 @@ Score: `(dimensions_covered / 6) * 40`. Validate technique application with `sun
43
43
 
44
44
  **Classification**: UI = static/always-same appearance. VAL = input validation/errors. LOGIC = behavior/state changes (includes persisted state without When). SEC = auth/permissions.
45
45
 
46
+ **Tier-aware scoring**: If the feature file only contains `@critical` + `@high` scenarios (Tier 1), do NOT penalize for missing VP-UI viewpoint — UI scenarios are intentionally deferred to Tier 2. Score "All applicable VP present" based on Tier 1-relevant viewpoints only (VAL, LOGIC, SEC). Note in the review output: *"VP-UI deferred to Tier 2 — run create-test with 'Add viewpoints' to expand."*
47
+
46
48
  ---
47
49
 
48
50
  ## Quality Rules
@@ -87,6 +89,21 @@ Do NOT mark `@manual` when data is visible in snapshot or documented in spec —
87
89
 
88
90
  ---
89
91
 
92
+ ## Unverified Selectors metric
93
+
94
+ **When to check**: if `qa/screens/<screen>/selectors/<screen>.yaml` exists, count lines matching the pattern `@needs-live-verify`.
95
+
96
+ | Metric | Source | Scoring impact |
97
+ |---|---|---|
98
+ | Unverified Selectors | Count of `@needs-live-verify` comment lines in `selectors.yaml` | None — non-blocking |
99
+
100
+ - If count > 0: include the following line in the review report summary (after the score table, before Issues):
101
+ `⚠ <N> selectors flagged @needs-live-verify — run run-test against live URL when available.`
102
+ - Does NOT reduce the score or block the 60% threshold. The suite can PASS with unverified selectors.
103
+ - If `selectors.yaml` does not exist yet (not yet generated): omit this metric entirely.
104
+
105
+ ---
106
+
90
107
  ## Output Format
91
108
 
92
109
  ```markdown
@@ -99,6 +116,9 @@ Do NOT mark `@manual` when data is visible in snapshot or documented in spec —
99
116
  | Viewpoint | <n> | 30 |
100
117
  | **Total** | **<n>%** | **100** |
101
118
 
119
+ ⚠ <N> selectors flagged @needs-live-verify — run run-test against live URL when available.
120
+ <!-- omit this line if selectors.yaml doesn't exist or @needs-live-verify count = 0 -->
121
+
102
122
  ### Issues
103
123
  1. [SYNTAX] ...
104
124
  2. [COVERAGE] ...
@@ -1,12 +1,27 @@
1
1
  import { test as base, expect } from '@playwright/test';
2
2
  import type { BrowserContext, Page } from '@playwright/test';
3
3
 
4
+ type CleanupConfig = {
5
+ overlay?: boolean;
6
+ forms?: boolean;
7
+ scroll?: boolean;
8
+ storage?: boolean;
9
+ };
10
+
4
11
  // Share one context per storageState — avoids creating multiple sessions
5
12
  // that trigger server rate limiting or session invalidation
6
13
  const contextCache = new Map<string, { context: BrowserContext; page: Page }>();
7
14
  const GOTO_PATCHED = Symbol('goto-patched');
8
15
 
9
- const test = base.extend({
16
+ const test = base.extend<{
17
+ autoCleanup: CleanupConfig;
18
+ screenshotOnFailure: boolean;
19
+ _autoCleanup: void;
20
+ _autoScreenshot: void;
21
+ }>({
22
+ autoCleanup: [{}, { option: true }],
23
+ screenshotOnFailure: [false, { option: true }],
24
+
10
25
  page: async ({ browser, storageState }, use) => {
11
26
  if (storageState) {
12
27
  const cacheKey = typeof storageState === 'string' ? storageState : JSON.stringify(storageState);
@@ -28,12 +43,6 @@ const test = base.extend({
28
43
  try {
29
44
  const currentPath = new URL(page.url()).pathname;
30
45
  if (currentPath === url || currentPath === url + '/') {
31
- // Dismiss any open overlays (dropdowns, dialogs) from previous test
32
- await page.keyboard.press('Escape').catch(() => {});
33
- await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
34
-
35
- // Safety check: if a fixed-position overlay (modal/dialog) is still present, full reload
36
- // eslint-disable-next-line no-eval -- runs in browser context via Playwright
37
46
  const hasOverlay = await page.evaluate(`(() => {
38
47
  const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
39
48
  if (!el) return false;
@@ -68,6 +77,55 @@ const test = base.extend({
68
77
  await context.close();
69
78
  }
70
79
  },
80
+
81
+ // Auto-cleanup fixture: runs teardown after each test based on @cleanup:* tags
82
+ _autoCleanup: [async ({ page, autoCleanup }, use) => {
83
+ await use();
84
+
85
+ if (autoCleanup.overlay) {
86
+ await page.keyboard.press('Escape').catch(() => {});
87
+ await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
88
+ // Dismiss persistent fixed overlays (modals, dialogs)
89
+ const hasOverlay = await page.evaluate(`(() => {
90
+ const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
91
+ if (!el) return false;
92
+ let current = el;
93
+ while (current && current !== document.body) {
94
+ if (getComputedStyle(current).position === 'fixed') return true;
95
+ current = current.parentElement;
96
+ }
97
+ return false;
98
+ })()`).catch(() => false);
99
+ if (hasOverlay) {
100
+ await page.keyboard.press('Escape').catch(() => {});
101
+ }
102
+ }
103
+ if (autoCleanup.forms) {
104
+ await page.evaluate(`(() => {
105
+ document.querySelectorAll('input:not([type=hidden]):not([type=submit])').forEach(el => { el.value = ''; });
106
+ document.querySelectorAll('textarea').forEach(el => { el.value = ''; });
107
+ document.querySelectorAll('select').forEach(el => { el.selectedIndex = 0; });
108
+ })()`).catch(() => {});
109
+ }
110
+ if (autoCleanup.scroll) {
111
+ await page.evaluate('window.scrollTo(0, 0)').catch(() => {});
112
+ }
113
+ if (autoCleanup.storage) {
114
+ await page.evaluate('sessionStorage.clear()').catch(() => {});
115
+ }
116
+ }, { auto: true }],
117
+
118
+ // Auto-screenshot fixture: captures screenshot on test failure
119
+ _autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
120
+ await use();
121
+
122
+ if (screenshotOnFailure && testInfo.status !== testInfo.expectedStatus) {
123
+ await testInfo.attach('screenshot', {
124
+ body: await page.screenshot(),
125
+ contentType: 'image/png',
126
+ });
127
+ }
128
+ }, { auto: true }],
71
129
  });
72
130
 
73
131
  export { test, expect };
@@ -0,0 +1,66 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import yaml from 'yaml';
4
+
5
+ export class TestDataLoader {
6
+ private data: Record<string, any>;
7
+
8
+ private constructor(data: Record<string, any>) {
9
+ this.data = data;
10
+ }
11
+
12
+ /**
13
+ * Load test data for a screen/feature combination.
14
+ *
15
+ * Priority (later wins):
16
+ * 1. {feature}.yaml — base data
17
+ * 2. {feature}.{SUNGEN_ENV}.yaml — environment-specific (if SUNGEN_ENV set)
18
+ */
19
+ static load(screenName: string, featureName: string): TestDataLoader {
20
+ const baseDir = path.join(process.cwd(), 'qa', 'screens', screenName, 'test-data');
21
+ const env = process.env.SUNGEN_ENV;
22
+
23
+ let data = loadYamlSync(path.join(baseDir, `${featureName}.yaml`)) || {};
24
+
25
+ if (env) {
26
+ const envData = loadYamlSync(path.join(baseDir, `${featureName}.${env}.yaml`));
27
+ if (envData) data = deepMerge(data, envData);
28
+ }
29
+
30
+ return new TestDataLoader(data);
31
+ }
32
+
33
+ get(key: string): string {
34
+ const parts = key.split('.');
35
+ let current: any = this.data;
36
+ for (const part of parts) {
37
+ if (current == null || typeof current !== 'object') {
38
+ throw new Error(`Test data key not found: ${key} (failed at '${part}')`);
39
+ }
40
+ current = current[part];
41
+ }
42
+ if (current === undefined || current === null) {
43
+ throw new Error(`Test data key not found: ${key}`);
44
+ }
45
+ return String(current);
46
+ }
47
+ }
48
+
49
+ function loadYamlSync(filePath: string): Record<string, any> | null {
50
+ if (!fs.existsSync(filePath)) return null;
51
+ const content = fs.readFileSync(filePath, 'utf-8');
52
+ return yaml.parse(content) || null;
53
+ }
54
+
55
+ function deepMerge(base: Record<string, any>, override: Record<string, any>): Record<string, any> {
56
+ const result = { ...base };
57
+ for (const [key, value] of Object.entries(override)) {
58
+ if (value && typeof value === 'object' && !Array.isArray(value) &&
59
+ result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])) {
60
+ result[key] = deepMerge(result[key], value);
61
+ } else {
62
+ result[key] = value;
63
+ }
64
+ }
65
+ return result;
66
+ }