@sun-asterisk/sungen 3.2.5 → 3.2.7

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 (126) hide show
  1. package/dist/cli/commands/delivery.d.ts.map +1 -1
  2. package/dist/cli/commands/delivery.js +7 -46
  3. package/dist/cli/commands/delivery.js.map +1 -1
  4. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  5. package/dist/dashboard/snapshot-builder.js +28 -6
  6. package/dist/dashboard/snapshot-builder.js.map +1 -1
  7. package/dist/dashboard/templates/index.html +20 -20
  8. package/dist/dashboard/types.d.ts +18 -0
  9. package/dist/dashboard/types.d.ts.map +1 -1
  10. package/dist/exporters/api-catalog-loader.d.ts +18 -0
  11. package/dist/exporters/api-catalog-loader.d.ts.map +1 -0
  12. package/dist/exporters/api-catalog-loader.js +77 -0
  13. package/dist/exporters/api-catalog-loader.js.map +1 -0
  14. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  15. package/dist/exporters/csv-exporter.js +20 -26
  16. package/dist/exporters/csv-exporter.js.map +1 -1
  17. package/dist/exporters/feature-parser.d.ts +6 -0
  18. package/dist/exporters/feature-parser.d.ts.map +1 -1
  19. package/dist/exporters/feature-parser.js +9 -3
  20. package/dist/exporters/feature-parser.js.map +1 -1
  21. package/dist/exporters/json-exporter.d.ts +10 -1
  22. package/dist/exporters/json-exporter.d.ts.map +1 -1
  23. package/dist/exporters/json-exporter.js +92 -50
  24. package/dist/exporters/json-exporter.js.map +1 -1
  25. package/dist/exporters/playwright-report-parser.d.ts +14 -0
  26. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  27. package/dist/exporters/playwright-report-parser.js +30 -0
  28. package/dist/exporters/playwright-report-parser.js.map +1 -1
  29. package/dist/exporters/result-variants.d.ts +29 -0
  30. package/dist/exporters/result-variants.d.ts.map +1 -0
  31. package/dist/exporters/result-variants.js +33 -0
  32. package/dist/exporters/result-variants.js.map +1 -0
  33. package/dist/exporters/xlsx-exporter.d.ts +18 -17
  34. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  35. package/dist/exporters/xlsx-exporter.js +25 -563
  36. package/dist/exporters/xlsx-exporter.js.map +1 -1
  37. package/dist/exporters/xlsx-report-builder.d.ts +196 -0
  38. package/dist/exporters/xlsx-report-builder.d.ts.map +1 -0
  39. package/dist/exporters/xlsx-report-builder.js +653 -0
  40. package/dist/exporters/xlsx-report-builder.js.map +1 -0
  41. package/dist/generators/test-generator/adapters/appium/templates/scenario.hbs +1 -1
  42. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +6 -6
  43. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +30 -8
  44. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +16 -1
  45. package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -1
  46. package/dist/generators/test-generator/patterns/expect-patterns.js +23 -0
  47. package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -1
  48. package/dist/generators/test-generator/utils/selector-resolver.d.ts +7 -0
  49. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  50. package/dist/generators/test-generator/utils/selector-resolver.js +13 -2
  51. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  52. package/dist/orchestrator/project-initializer.d.ts +9 -0
  53. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  54. package/dist/orchestrator/project-initializer.js +66 -9
  55. package/dist/orchestrator/project-initializer.js.map +1 -1
  56. package/dist/orchestrator/templates/ai-src/commands/add-api.md +76 -0
  57. package/dist/orchestrator/templates/ai-src/commands/create-test.md +2 -2
  58. package/dist/orchestrator/templates/ai-src/config/codex.md +3 -2
  59. package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +73 -0
  60. package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +42 -8
  61. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +21 -0
  62. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +2 -0
  63. package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +33 -0
  64. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +6 -0
  65. package/dist/orchestrator/templates/specs-api.d.ts +26 -0
  66. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
  67. package/dist/orchestrator/templates/specs-api.js +131 -3
  68. package/dist/orchestrator/templates/specs-api.js.map +1 -1
  69. package/dist/orchestrator/templates/specs-api.ts +118 -5
  70. package/dist/orchestrator/templates/specs-test-data.ts +20 -1
  71. package/package.json +3 -3
  72. package/src/cli/commands/delivery.ts +8 -42
  73. package/src/dashboard/snapshot-builder.ts +31 -7
  74. package/src/dashboard/templates/index.html +20 -20
  75. package/src/dashboard/types.ts +19 -0
  76. package/src/exporters/api-catalog-loader.ts +44 -0
  77. package/src/exporters/csv-exporter.ts +19 -24
  78. package/src/exporters/feature-parser.ts +8 -2
  79. package/src/exporters/json-exporter.ts +102 -51
  80. package/src/exporters/playwright-report-parser.ts +27 -0
  81. package/src/exporters/result-variants.ts +47 -0
  82. package/src/exporters/xlsx-exporter.ts +34 -623
  83. package/src/exporters/xlsx-report-builder.ts +806 -0
  84. package/src/generators/test-generator/adapters/appium/templates/scenario.hbs +1 -1
  85. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +6 -6
  86. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +30 -8
  87. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +16 -1
  88. package/src/generators/test-generator/patterns/expect-patterns.ts +23 -0
  89. package/src/generators/test-generator/utils/selector-resolver.ts +30 -2
  90. package/src/orchestrator/project-initializer.ts +67 -9
  91. package/src/orchestrator/templates/ai-src/commands/add-api.md +76 -0
  92. package/src/orchestrator/templates/ai-src/commands/create-test.md +2 -2
  93. package/src/orchestrator/templates/ai-src/config/codex.md +3 -2
  94. package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +73 -0
  95. package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +42 -8
  96. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +21 -0
  97. package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +2 -0
  98. package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +33 -0
  99. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +6 -0
  100. package/src/orchestrator/templates/specs-api.ts +118 -5
  101. package/src/orchestrator/templates/specs-test-data.ts +20 -1
  102. package/dist/cli/types.d.ts +0 -9
  103. package/dist/cli/types.d.ts.map +0 -1
  104. package/dist/cli/types.js +0 -7
  105. package/dist/cli/types.js.map +0 -1
  106. package/dist/generators/gherkin-parser/selector-extractor.d.ts +0 -37
  107. package/dist/generators/gherkin-parser/selector-extractor.d.ts.map +0 -1
  108. package/dist/generators/gherkin-parser/selector-extractor.js +0 -108
  109. package/dist/generators/gherkin-parser/selector-extractor.js.map +0 -1
  110. package/dist/generators/test-generator/types.d.ts +0 -25
  111. package/dist/generators/test-generator/types.d.ts.map +0 -1
  112. package/dist/generators/test-generator/types.js +0 -7
  113. package/dist/generators/test-generator/types.js.map +0 -1
  114. package/dist/generators/types.d.ts +0 -119
  115. package/dist/generators/types.d.ts.map +0 -1
  116. package/dist/generators/types.js +0 -48
  117. package/dist/generators/types.js.map +0 -1
  118. package/dist/orchestrator/figma/node-path-collapser.d.ts +0 -16
  119. package/dist/orchestrator/figma/node-path-collapser.d.ts.map +0 -1
  120. package/dist/orchestrator/figma/node-path-collapser.js +0 -37
  121. package/dist/orchestrator/figma/node-path-collapser.js.map +0 -1
  122. package/src/cli/types.ts +0 -9
  123. package/src/generators/gherkin-parser/selector-extractor.ts +0 -142
  124. package/src/generators/test-generator/types.ts +0 -26
  125. package/src/generators/types.ts +0 -227
  126. package/src/orchestrator/figma/node-path-collapser.ts +0 -38
@@ -118,6 +118,27 @@ Scenario: ...
118
118
 
119
119
  Path access on a bound result: `{{q.count}}`/`{{q.length}}`, `{{q.first.col}}`, `{{q.last.col}}`, `{{q[2].col}}`, `{{q.col}}` (= first row's col). `expect A is B` also supports `is at least` / `is at most` / `is not`. Tier-2 declarative (trivial inline, no catalog): `User see [<table>] row where [<col>] is {{v}} [has [<col2>] = "x"]`, `… no row where …`, `… count is {{n}}`. Full grammar + catalog/datasource/secret rules → **Advanced → Database** doc. Only emit DB steps when the project has a `database/` catalog / `datasources.yaml`.
120
120
 
121
+ ### API response assertions (optional API Driver)
122
+
123
+ An `@api:<name>` call binds the response to `{{name}}` = `{ status, ok, body, headers }` (+ `ok_count`/`status_counts` under `@concurrent`). Assert with the same `expect A is B` grammar + path access:
124
+
125
+ ```gherkin
126
+ @api:create_user(email={{email}}) @query:user_by_email(email={{email}})
127
+ Scenario: ...
128
+ Then expect {{create_user.status}} is 201 # status
129
+ And expect {{create_user.body.id}} is at least {{one}} # server-derived body field
130
+ And expect {{user_by_email.count}} is {{one}} # proof of effect (DB oracle)
131
+ ```
132
+
133
+ Path access: `{{name.status}}`, `{{name.body.<path>}}` (dot/`[i]` nav), `{{name.headers.x-request-id}}` (dot form — hyphens are fine, no quotes/brackets), `{{name.ok_count}}`.
134
+
135
+ Full-shape contract check: `expect {{name.body}} matches schema [Ref]` validates the response body against the named JSON Schema in `qa/api/schemas.yaml` (populated by `sungen api import`). Use alongside a couple of concrete field asserts, not instead of them.
136
+
137
+ **Depth rule** (see `sungen-tc-review` API rubric):
138
+ - **Success (2xx)** → assert status **and** ≥1 body field; a **mutating** success must also prove the effect (`@query` read-back, follow-up GET, or `@concurrent` `ok_count`). Never status-only.
139
+ - **Error (4xx/5xx)** → assert the status (via `@cases` `expect_status` or an explicit `is 4xx`); assert the error message field when the contract defines one.
140
+ - **Anti-pattern** — re-asserting the value you just sent (`{{x.body.email}} is {{email}}` on the thing you created) proves little; assert a **server-derived** field (id, timestamp, computed status) or read it back.
141
+
121
142
  ### States
122
143
 
123
144
  `hidden` `visible` `disabled` `enabled` `checked` `unchecked` `focused` `empty` `loading` `selected` `sorted ascending` `sorted descending`
@@ -84,6 +84,7 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
84
84
  - **Typical mismatch cases**: Gherkin uses English label (`[Submit]`) but app displays Vietnamese (`"Gửi"`); placeholder is descriptive (`"Nhập email của bạn"`) not a bare field name (`"Email"`); button text includes an icon glyph before/after the word.
85
85
  7. **Substring ambiguity check**: for each `role` + `name` selector, check if any other element in the snapshot has a name that **contains** this name as a substring (e.g., `"Đăng ký"` vs `"Đăng ký bằng Google"`). If yes → add `exact: true` to prevent strict mode violation at runtime.
86
86
  8. **Merge, don't overwrite**: preserve the page selector and any user-authored entries in `selectors.yaml`. Only add missing keys.
87
+ - **Type-collision check** (see `sungen-selector-keys` § Collision rule): before writing a base key, check whether it already exists with a *different* `type`. If so, give **both** entries a `--type` suffix instead of clobbering or sharing the base key — a base-key entry of the wrong type gets picked up by any step referencing that label. Watch `page` especially: a `[X] page` step reads `value` as a URL, so if `X` already holds a button (`value: 'button'`) the assertion compiles to `toHaveURL(/button/)`. Write `X--page` (URL from the live page / feature `Path:`) alongside `X--button`.
87
88
  9. **Show summary + confirm**: list the keys that will be added, ask the user to approve, then write the file.
88
89
  10. **Compile**: **Screen**: `sungen generate --screen <screen>`. **Flow**: `sungen generate --flow <flow>`. Then proceed to Phase 1.
89
90
 
@@ -97,6 +98,7 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
97
98
  - Writing XPath or class-based CSS selectors → breaks on DOM/style refactoring. Use role/testid/text/label/placeholder from the accessibility tree.
98
99
  - Falling back to `locator: 'div.some-class > span'` when the element IS visible in the accessibility snapshot with a role + name → the snapshot gives you `getByRole` for free; use it.
99
100
  - Overwriting user-authored selectors → always merge.
101
+ - Reusing a base key for two element types (e.g. a `[X] button` and a `[X] page`) → the wrong-type entry wins on lookup; disambiguate with `--type` suffixes (see step 8 type-collision check).
100
102
 
101
103
  ---
102
104
 
@@ -88,6 +88,39 @@ add campaign--text:
88
88
  value: 'Add Campaign'
89
89
  ```
90
90
 
91
+ ### Collision rule (MANDATORY)
92
+
93
+ **Before writing a base key, check whether it already exists with a *different* `type`.**
94
+ If it does, DO NOT overwrite it and DO NOT let the two share the base key — give **each** entry
95
+ its own `--type` suffix. A key match alone does not carry type: the resolver walks
96
+ `key--type` → `key` (see § Lookup Priority), so a base-key entry of the wrong type will be
97
+ picked up by any step referencing that label, regardless of the type the step expects.
98
+
99
+ The `page` type is the sharpest trap. A page step (`Then User see [X] page`) reads the entry's
100
+ `value` as a **URL**. If key `X` already holds a `role`/button entry, the page step resolves that
101
+ button and emits `toHaveURL(/button/)` — a literal button label treated as a URL path.
102
+
103
+ ```yaml
104
+ # Gherkin: `When User click [読取結果一覧] button` AND `Then User see [読取結果一覧] page`
105
+ # WRONG — one base key, page step inherits the button's value ('button') as a URL:
106
+ 読取結果一覧:
107
+ type: 'role'
108
+ value: 'button'
109
+ name: '読取結果一覧'
110
+
111
+ # RIGHT — disambiguate by type; the page entry carries the real URL:
112
+ 読取結果一覧--button:
113
+ type: 'role'
114
+ value: 'button'
115
+ name: '読取結果一覧'
116
+
117
+ 読取結果一覧--page:
118
+ type: 'page'
119
+ value: '/reading-results' # captured from the live page, or the feature Path:
120
+ ```
121
+
122
+ If the label is used by only ONE type, the base key (no suffix) is fine — suffix only on collision.
123
+
91
124
  Type aliases (normalized automatically):
92
125
  | Gherkin type | Normalized suffix |
93
126
  |---|---|
@@ -159,6 +159,12 @@ Build a mapping table: for each applicable group, does the feature have a matchi
159
159
  | **Syntax compliance** — steps match `sungen-gherkin-syntax` patterns, correct `[Ref] type with {{v}}`, no bare `is visible` | 4 |
160
160
  | Grouping — each case has 2–7 related assertions for the same action | 4 |
161
161
 
162
+ **API scenarios (`@api`) — depth rubric:** a success case scores on Assertion Quality only if it proves the effect, not just the status. Apply per scenario:
163
+ - **status-only mutating success = fail the "Specific" check** — `expect {{x.status}} is 201` alone proves nothing happened. Require a server-derived body field **and** a proof of effect (`@query` read-back / follow-up GET / `@concurrent` `ok_count`).
164
+ - **read contract** — status + ≥1 body field (medium is acceptable for reads).
165
+ - **error (4xx)** — the status IS the assertion; also check the error-message field when the contract defines one.
166
+ - **anti-pattern (counts against "Avoids anti-patterns")** — re-asserting the input just sent instead of a server-derived value.
167
+
162
168
  ### Test Data (10)
163
169
 
164
170
  | Check | Pts |
@@ -48,6 +48,37 @@ function loadConfig(): Record<string, ApiDataSource> {
48
48
  function substitute(text: string, params: Record<string, any>): string {
49
49
  return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => encodeURIComponent(String(params[p] ?? '')));
50
50
  }
51
+ /** Bind `:param` tokens WITHOUT URL-encoding — for headers, body-file paths, and multipart file paths. */
52
+ function substituteRaw(text: string, params: Record<string, any>): string {
53
+ return text.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => String(params[p] ?? ''));
54
+ }
55
+ /** Delete any case-insensitive variant of a header (HTTP header names are case-insensitive, but a
56
+ * plain object keys on exact case — so a datasource `Content-Type` and our `content-type` would
57
+ * both be sent). Call before setting, or to let Playwright own the header (multipart boundary). */
58
+ function deleteHeader(headers: Record<string, string>, name: string): void {
59
+ const lower = name.toLowerCase();
60
+ for (const k of Object.keys(headers)) if (k.toLowerCase() === lower) delete headers[k];
61
+ }
62
+
63
+ /** A multipart file field embedded by the compiler (string = shorthand for { path }). */
64
+ type FileSpec = string | { path: string; mimeType?: string; filename?: string; name?: string };
65
+
66
+ const MIME: Record<string, string> = {
67
+ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',
68
+ svg: 'image/svg+xml', pdf: 'application/pdf', csv: 'text/csv', txt: 'text/plain',
69
+ json: 'application/json', xml: 'application/xml', zip: 'application/zip', mp4: 'video/mp4',
70
+ };
71
+ export function inferMime(name: string): string {
72
+ return MIME[(name.split('.').pop() || '').toLowerCase()] || 'application/octet-stream';
73
+ }
74
+ /** Resolve a fixture path: cwd-relative/absolute first, else qa/fixtures/<path>. */
75
+ export function resolveFixture(p: string, label: string): string {
76
+ const direct = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
77
+ if (fs.existsSync(direct)) return direct;
78
+ const inFixtures = path.join(process.cwd(), 'qa', 'fixtures', p);
79
+ if (fs.existsSync(inFixtures)) return inFixtures;
80
+ throw new Error(`API Driver: ${label} — fixture "${p}" not found (looked in ${direct} and qa/fixtures/${p}).`);
81
+ }
51
82
 
52
83
  /**
53
84
  * Join a datasource base URL with a catalog path. Concatenate rather than rely on Playwright's
@@ -82,7 +113,7 @@ class ApiClient {
82
113
  */
83
114
  async call(
84
115
  label: string,
85
- req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; headers?: Record<string, string>; datasource?: string },
116
+ req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; files?: Record<string, FileSpec>; bodyFile?: FileSpec; headers?: Record<string, string>; timeout?: number; datasource?: string },
86
117
  params: Record<string, any> = {},
87
118
  opts: { storageState?: string } = {},
88
119
  ): Promise<{ status: number; ok: boolean; body: any; headers: Record<string, string> }> {
@@ -94,7 +125,7 @@ class ApiClient {
94
125
  const headers: Record<string, string> = { ...(conf.headers || {}) };
95
126
  // catalog headers; :param tokens bind at runtime — raw (no URL-encoding, unlike the path)
96
127
  for (const [k, v] of Object.entries(req.headers || {}))
97
- headers[k] = String(v).replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p) => String(params[p] ?? ''));
128
+ headers[k] = substituteRaw(String(v), params);
98
129
  // Body: substitute `:param` into the body template (object values), then encode per `encoding`.
99
130
  let body: any;
100
131
  if (req.body !== undefined && req.body !== null) {
@@ -102,8 +133,39 @@ class ApiClient {
102
133
  }
103
134
  // Map the wire format to the right Playwright option (#345): json → data (application/json,
104
135
  // default), form → form (application/x-www-form-urlencoded), multipart → multipart (form-data).
136
+ // A `files` block is a real upload: read each fixture into a { name, mimeType, buffer } part and
137
+ // send multipart, with any `body` values riding along as text fields.
105
138
  const bodyOpt: Record<string, unknown> = {};
106
- if (body !== undefined) {
139
+ if (req.bodyFile) {
140
+ // Raw-binary body: the whole request body is the fixture's bytes. Content-Type comes from the
141
+ // spec's mimeType (default application/octet-stream) — this is what octet-stream upload
142
+ // endpoints expect (a multipart body would be rejected 415).
143
+ const spec = typeof req.bodyFile === 'string' ? { path: req.bodyFile } : req.bodyFile;
144
+ const fpath = substituteRaw(String(spec.path), params);
145
+ if (fpath) {
146
+ const resolved = resolveFixture(fpath, label);
147
+ bodyOpt.data = fs.readFileSync(resolved);
148
+ // Own the Content-Type: drop any datasource default (case-insensitive) so we don't send both.
149
+ deleteHeader(headers, 'content-type');
150
+ headers['content-type'] = spec.mimeType ?? 'application/octet-stream';
151
+ }
152
+ } else if (req.files && Object.keys(req.files).length) {
153
+ const mp: Record<string, any> = {};
154
+ if (body && typeof body === 'object')
155
+ for (const [k, v] of Object.entries(body as Record<string, unknown>)) mp[k] = typeof v === 'string' ? v : JSON.stringify(v);
156
+ for (const [field, raw] of Object.entries(req.files)) {
157
+ const spec = typeof raw === 'string' ? { path: raw } : raw;
158
+ const fpath = substituteRaw(String(spec.path), params);
159
+ if (!fpath) continue; // empty param → omit the part (a missing-file / wrong-type error row)
160
+ const resolved = resolveFixture(fpath, label);
161
+ const filename = spec.filename ?? path.basename(resolved);
162
+ mp[spec.name ?? field] = { name: filename, mimeType: spec.mimeType ?? inferMime(filename), buffer: fs.readFileSync(resolved) };
163
+ }
164
+ // Playwright generates the multipart/form-data Content-Type (with boundary); drop any datasource
165
+ // default Content-Type so it doesn't conflict with / duplicate the generated one.
166
+ deleteHeader(headers, 'content-type');
167
+ bodyOpt.multipart = mp;
168
+ } else if (body !== undefined) {
107
169
  const enc = req.encoding ?? 'json';
108
170
  if (enc === 'form') bodyOpt.form = body;
109
171
  else if (enc === 'multipart') bodyOpt.multipart = body;
@@ -115,7 +177,7 @@ class ApiClient {
115
177
  // authenticated cookies. Disposed per call so no request context lingers and hangs the process.
116
178
  const ctx: APIRequestContext = await request.newContext({
117
179
  extraHTTPHeaders: headers,
118
- timeout: conf.timeout_ms ?? 15000,
180
+ timeout: req.timeout ?? conf.timeout_ms ?? 15000, // per-endpoint override → datasource → 15s
119
181
  ...(opts.storageState ? { storageState: opts.storageState } : {}),
120
182
  });
121
183
  try {
@@ -139,7 +201,7 @@ class ApiClient {
139
201
  */
140
202
  async callN(
141
203
  label: string,
142
- req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; headers?: Record<string, string>; datasource?: string },
204
+ req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; files?: Record<string, FileSpec>; bodyFile?: FileSpec; headers?: Record<string, string>; timeout?: number; datasource?: string },
143
205
  params: Record<string, any> = {},
144
206
  n = 1,
145
207
  opts: { storageState?: string } = {},
@@ -160,6 +222,57 @@ class ApiClient {
160
222
  statuses: responses.map((r) => r.status),
161
223
  };
162
224
  }
225
+
226
+ /**
227
+ * Validate a response body against a named JSON Schema from `qa/api/schemas.yaml` (populated by
228
+ * `sungen api import` from the OpenAPI `components.schemas`). Backs `expect {{name.body}} matches
229
+ * schema [Ref]` — a full-contract check beyond individual field asserts. Returns `{ ok, errors }`;
230
+ * the generated assertion feeds `errors` into the failure message. Uses Ajv (a QA-project dep).
231
+ */
232
+ assertSchema(value: unknown, schemaName: string): { ok: boolean; errors: string } {
233
+ return assertSchemaImpl(value, schemaName);
234
+ }
235
+ }
236
+
237
+ // ── Response-schema validation (Ajv) ─────────────────────────────────────────────────────────────
238
+ // Schemas live in qa/api/schemas.yaml (name → JSON Schema, incl. #/components/schemas/* $refs).
239
+ // Loaded + compiled lazily so a project that never asserts a schema needs no Ajv resolution.
240
+ let _apiSchemas: Record<string, any> | null | undefined;
241
+ function loadApiSchemas(): Record<string, any> {
242
+ if (_apiSchemas !== undefined) return _apiSchemas || {};
243
+ const file = [path.join(process.cwd(), 'qa', 'api', 'schemas.yaml'), path.join(process.cwd(), 'schemas.yaml')].find((f) => fs.existsSync(f));
244
+ if (!file) { _apiSchemas = null; return {}; }
245
+ const { parse } = require('yaml');
246
+ _apiSchemas = (parse(fs.readFileSync(file, 'utf8')) as Record<string, any>) || {};
247
+ return _apiSchemas || {};
248
+ }
249
+
250
+ let _ajv: any;
251
+ function getAjv(): any {
252
+ if (_ajv) return _ajv;
253
+ const Ajv = require('ajv');
254
+ _ajv = new Ajv({ allErrors: true, strict: false });
255
+ try { require('ajv-formats')(_ajv); } catch { /* formats optional */ }
256
+ // Register every named schema under its component ref so inter-schema $refs resolve.
257
+ for (const [name, schema] of Object.entries(loadApiSchemas())) {
258
+ try { _ajv.addSchema(schema, `#/components/schemas/${name}`); } catch { /* dup/invalid → skip */ }
259
+ }
260
+ return _ajv;
261
+ }
262
+
263
+ function assertSchemaImpl(value: unknown, schemaName: string): { ok: boolean; errors: string } {
264
+ const schema = loadApiSchemas()[schemaName];
265
+ if (!schema) return { ok: false, errors: `schema "${schemaName}" not found in qa/api/schemas.yaml` };
266
+ let validate: any;
267
+ try {
268
+ const ajv = getAjv();
269
+ validate = ajv.getSchema(`#/components/schemas/${schemaName}`) || ajv.compile(schema);
270
+ } catch (e: any) {
271
+ return { ok: false, errors: `schema "${schemaName}" failed to compile: ${e?.message || e}` };
272
+ }
273
+ const ok = validate(value) === true;
274
+ const errors = ok ? '' : (validate.errors || []).map((e: any) => `${e.instancePath || '(root)'} ${e.message}`).join('; ') || 'did not match schema';
275
+ return { ok, errors };
163
276
  }
164
277
 
165
278
  export const api = new ApiClient();
@@ -48,7 +48,26 @@ export class TestDataLoader {
48
48
  if (value === undefined || value === null) {
49
49
  throw new Error(`Test data key not found: ${key}`);
50
50
  }
51
- return String(value);
51
+ return this.interpolate(String(value));
52
+ }
53
+
54
+ /**
55
+ * Substitute embedded `{{ref}}` cross-references inside a resolved string value.
56
+ * Each ref is looked up via resolve() (flat key, @cases row column, or dotted path),
57
+ * so `expected_operator_id_label: 'ID : {{operator.id}}'` yields the real value at
58
+ * runtime — matching the delivery report's substituteTestDataVars. `$`-prefixed
59
+ * dynamic vars ({{$timestamp}} …) are already substituted at load time and never
60
+ * match `[\w.]+`, so they are left untouched. Resolution recurses (a referenced value
61
+ * may itself hold refs) up to a depth cap that guards against reference cycles; an
62
+ * unknown or empty ref is left as the literal `{{ref}}` — never blanked.
63
+ */
64
+ private interpolate(value: string, depth: number = 0): string {
65
+ if (depth > 10 || value.indexOf('{{') === -1) return value;
66
+ return value.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (match, ref: string) => {
67
+ const resolved = this.resolve(ref);
68
+ if (resolved === undefined || resolved === null || resolved === '') return match;
69
+ return this.interpolate(String(resolved), depth + 1);
70
+ });
52
71
  }
53
72
 
54
73
  /**
@@ -1,9 +0,0 @@
1
- /**
2
- * CLI Types
3
- * Future adapter interface for programmatic command execution (e.g., VSCode extension)
4
- */
5
- export interface CommandAdapter {
6
- name: string;
7
- execute(command: string, options: Record<string, any>): Promise<void>;
8
- }
9
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE"}
package/dist/cli/types.js DELETED
@@ -1,7 +0,0 @@
1
- "use strict";
2
- /**
3
- * CLI Types
4
- * Future adapter interface for programmatic command execution (e.g., VSCode extension)
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -1,37 +0,0 @@
1
- /**
2
- * Gherkin Selector Extractor
3
- * Scans Gherkin feature files and extracts all [screen.element] references
4
- */
5
- export interface ExtractedSelector {
6
- screenId: string;
7
- elementId: string;
8
- fullRef: string;
9
- occurrences: number;
10
- }
11
- export interface SelectorsByScreen {
12
- [screenId: string]: {
13
- [elementId: string]: {
14
- fullRef: string;
15
- occurrences: number;
16
- };
17
- };
18
- }
19
- export declare class GherkinSelectorExtractor {
20
- /**
21
- * Extract all selector references from Gherkin feature files
22
- */
23
- extractSelectors(featuresDir: string): SelectorsByScreen;
24
- /**
25
- * Extract selectors from feature file content
26
- */
27
- private extractSelectorsFromContent;
28
- /**
29
- * Generate base skeleton YAML structure
30
- */
31
- generateSkeletonYaml(screenId: string, selectors: SelectorsByScreen): string;
32
- /**
33
- * Write skeleton files for all screens
34
- */
35
- writeSkeletonFiles(selectors: SelectorsByScreen, outputDir: string): void;
36
- }
37
- //# sourceMappingURL=selector-extractor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"selector-extractor.d.ts","sourceRoot":"","sources":["../../../src/generators/gherkin-parser/selector-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,QAAQ,EAAE,MAAM,GAAG;QAClB,CAAC,SAAS,EAAE,MAAM,GAAG;YACnB,OAAO,EAAE,MAAM,CAAC;YAChB,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;KACH,CAAC;CACH;AAED,qBAAa,wBAAwB;IACnC;;OAEG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB;IAgCxD;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA0BnC;;OAEG;IACH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,iBAAiB,GAAG,MAAM;IA+B5E;;OAEG;IACH,kBAAkB,CAChB,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,GAChB,IAAI;CAWR"}
@@ -1,108 +0,0 @@
1
- "use strict";
2
- /**
3
- * Gherkin Selector Extractor
4
- * Scans Gherkin feature files and extracts all [screen.element] references
5
- */
6
- var __importDefault = (this && this.__importDefault) || function (mod) {
7
- return (mod && mod.__esModule) ? mod : { "default": mod };
8
- };
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.GherkinSelectorExtractor = void 0;
11
- const fs_1 = __importDefault(require("fs"));
12
- const path_1 = __importDefault(require("path"));
13
- const glob_1 = require("glob");
14
- class GherkinSelectorExtractor {
15
- /**
16
- * Extract all selector references from Gherkin feature files
17
- */
18
- extractSelectors(featuresDir) {
19
- const featureFiles = glob_1.glob.sync('**/*.feature', {
20
- cwd: featuresDir,
21
- absolute: true,
22
- });
23
- const selectorsByScreen = {};
24
- for (const filePath of featureFiles) {
25
- const content = fs_1.default.readFileSync(filePath, 'utf-8');
26
- const selectors = this.extractSelectorsFromContent(content);
27
- // Merge into selectorsByScreen
28
- for (const selector of selectors) {
29
- if (!selectorsByScreen[selector.screenId]) {
30
- selectorsByScreen[selector.screenId] = {};
31
- }
32
- if (!selectorsByScreen[selector.screenId][selector.elementId]) {
33
- selectorsByScreen[selector.screenId][selector.elementId] = {
34
- fullRef: selector.fullRef,
35
- occurrences: 0,
36
- };
37
- }
38
- selectorsByScreen[selector.screenId][selector.elementId].occurrences++;
39
- }
40
- }
41
- return selectorsByScreen;
42
- }
43
- /**
44
- * Extract selectors from feature file content
45
- */
46
- extractSelectorsFromContent(content) {
47
- const selectors = [];
48
- // Regex to match [screen.element] or [screen]
49
- const selectorRegex = /\[([a-zA-Z0-9_-]+)(?:\.([a-zA-Z0-9_-]+))?\]/g;
50
- let match;
51
- while ((match = selectorRegex.exec(content)) !== null) {
52
- const fullRef = match[0]; // e.g., "[auth-login.email]"
53
- const screenId = match[1]; // e.g., "auth-login"
54
- const elementId = match[2]; // e.g., "email" (undefined for route-only refs)
55
- // Only include selectors with both screen and element (skip route-only refs)
56
- if (elementId) {
57
- selectors.push({
58
- screenId,
59
- elementId,
60
- fullRef,
61
- occurrences: 1,
62
- });
63
- }
64
- }
65
- return selectors;
66
- }
67
- /**
68
- * Generate base skeleton YAML structure
69
- */
70
- generateSkeletonYaml(screenId, selectors) {
71
- const screenSelectors = selectors[screenId];
72
- if (!screenSelectors) {
73
- return `# No selectors found for screen: ${screenId}\n`;
74
- }
75
- const lines = [
76
- `# Selector skeleton for screen: ${screenId}`,
77
- `# AI will fill in the selector values based on UI model`,
78
- `# This file is generated by scanning Gherkin feature files`,
79
- '',
80
- ];
81
- // Sort by element ID for consistency
82
- const sortedElementIds = Object.keys(screenSelectors).sort();
83
- for (const elementId of sortedElementIds) {
84
- const { fullRef, occurrences } = screenSelectors[elementId];
85
- lines.push(`"${fullRef}":`);
86
- lines.push(` selector: ""`);
87
- lines.push(` type: ""`);
88
- lines.push(` description: ""`);
89
- lines.push(` # Referenced ${occurrences} time(s) in Gherkin`);
90
- lines.push('');
91
- }
92
- return lines.join('\n');
93
- }
94
- /**
95
- * Write skeleton files for all screens
96
- */
97
- writeSkeletonFiles(selectors, outputDir) {
98
- // Ensure output directory exists
99
- fs_1.default.mkdirSync(outputDir, { recursive: true });
100
- for (const screenId of Object.keys(selectors)) {
101
- const skeletonContent = this.generateSkeletonYaml(screenId, selectors);
102
- const outputPath = path_1.default.join(outputDir, `${screenId}.skeleton.yaml`);
103
- fs_1.default.writeFileSync(outputPath, skeletonContent, 'utf-8');
104
- }
105
- }
106
- }
107
- exports.GherkinSelectorExtractor = GherkinSelectorExtractor;
108
- //# sourceMappingURL=selector-extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"selector-extractor.js","sourceRoot":"","sources":["../../../src/generators/gherkin-parser/selector-extractor.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,4CAAoB;AACpB,gDAAwB;AACxB,+BAA4B;AAkB5B,MAAa,wBAAwB;IACnC;;OAEG;IACH,gBAAgB,CAAC,WAAmB;QAClC,MAAM,YAAY,GAAG,WAAI,CAAC,IAAI,CAAC,cAAc,EAAE;YAC7C,GAAG,EAAE,WAAW;YAChB,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,MAAM,iBAAiB,GAAsB,EAAE,CAAC;QAEhD,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,SAAS,GAAG,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;YAE5D,+BAA+B;YAC/B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC1C,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;gBAC5C,CAAC;gBAED,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC9D,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG;wBACzD,OAAO,EAAE,QAAQ,CAAC,OAAO;wBACzB,WAAW,EAAE,CAAC;qBACf,CAAC;gBACJ,CAAC;gBAED,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;YACzE,CAAC;QACH,CAAC;QAED,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED;;OAEG;IACK,2BAA2B,CAAC,OAAe;QACjD,MAAM,SAAS,GAAwB,EAAE,CAAC;QAE1C,8CAA8C;QAC9C,MAAM,aAAa,GAAG,8CAA8C,CAAC;QAErE,IAAI,KAAK,CAAC;QACV,OAAO,CAAC,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,6BAA6B;YACvD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,qBAAqB;YAChD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,gDAAgD;YAE5E,6EAA6E;YAC7E,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,CAAC,IAAI,CAAC;oBACb,QAAQ;oBACR,SAAS;oBACT,OAAO;oBACP,WAAW,EAAE,CAAC;iBACf,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,QAAgB,EAAE,SAA4B;QACjE,MAAM,eAAe,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QAE5C,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,oCAAoC,QAAQ,IAAI,CAAC;QAC1D,CAAC;QAED,MAAM,KAAK,GAAa;YACtB,mCAAmC,QAAQ,EAAE;YAC7C,yDAAyD;YACzD,4DAA4D;YAC5D,EAAE;SACH,CAAC;QAEF,qCAAqC;QACrC,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,EAAE,CAAC;QAE7D,KAAK,MAAM,SAAS,IAAI,gBAAgB,EAAE,CAAC;YACzC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;YAE5D,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,kBAAkB,WAAW,qBAAqB,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,kBAAkB,CAChB,SAA4B,EAC5B,SAAiB;QAEjB,iCAAiC;QACjC,YAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,oBAAoB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YACvE,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,gBAAgB,CAAC,CAAC;YAErE,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF;AApHD,4DAoHC"}
@@ -1,25 +0,0 @@
1
- /**
2
- * Test Generator Types
3
- * Core type definitions for the test generation compiler
4
- */
5
- /**
6
- * Generated Test Result
7
- */
8
- export interface GeneratedTest {
9
- featureName: string;
10
- filePath: string;
11
- code: string;
12
- sourceFile?: string;
13
- stepCount?: number;
14
- }
15
- /**
16
- * Generation Statistics
17
- */
18
- export interface GenerationStats {
19
- totalScenarios: number;
20
- totalSteps: number;
21
- dictionaryMatched: number;
22
- todoCount: number;
23
- generationTime: number;
24
- }
25
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB"}
@@ -1,7 +0,0 @@
1
- "use strict";
2
- /**
3
- * Test Generator Types
4
- * Core type definitions for the test generation compiler
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/generators/test-generator/types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -1,119 +0,0 @@
1
- /**
2
- * Core types for Selector DSL Framework
3
- * Framework-agnostic types for multi-platform support
4
- */
5
- export interface UIElement {
6
- key: string;
7
- tag: string;
8
- role?: string;
9
- name?: string;
10
- id?: string;
11
- placeholder?: string;
12
- label?: string;
13
- text?: string;
14
- ariaLabel?: string;
15
- testId?: string;
16
- source: string;
17
- framework?: string;
18
- props?: Record<string, any>;
19
- }
20
- export interface UIModel {
21
- screenId: string;
22
- routeId: string;
23
- framework: FrameworkType;
24
- sourceFiles: string[];
25
- elements: UIElement[];
26
- metadata?: {
27
- scannedAt: string;
28
- version: string;
29
- hash: string;
30
- };
31
- }
32
- export type DiscoveryMethod = 'ai-static-analysis' | 'manual' | 'runtime';
33
- export type LocatorType = 'css' | 'xpath' | 'resource-id' | 'accessibility-id' | 'test-id' | 'key' | 'text' | 'role';
34
- export interface SelectorEntry {
35
- selector: string;
36
- type: string;
37
- description: string;
38
- discoveryMethod: DiscoveryMethod;
39
- locatorType?: LocatorType;
40
- confidence?: 'high' | 'medium' | 'low';
41
- alternatives?: string[];
42
- metadata?: {
43
- lastValidated?: string;
44
- validationStatus?: 'valid' | 'invalid' | 'unknown';
45
- };
46
- }
47
- export interface SelectorDSL {
48
- [elementId: string]: SelectorEntry;
49
- }
50
- export type FrameworkType = 'react' | 'react-nextjs' | 'vue' | 'angular' | 'html' | 'flutter' | 'jetpack-compose' | 'swiftui';
51
- export type TargetPlatform = 'web' | 'android' | 'ios' | 'desktop';
52
- export type ExecutorType = 'playwright' | 'puppeteer' | 'selenium' | 'appium-android' | 'appium-ios' | 'flutter-driver' | 'espresso' | 'xcuitest';
53
- export interface ExecutorConfig {
54
- type: ExecutorType;
55
- platform: TargetPlatform;
56
- selectorPath: string;
57
- overridePath?: string;
58
- }
59
- export interface CacheEntry {
60
- screenId: string;
61
- hash: string;
62
- selectors: SelectorDSL;
63
- metadata: {
64
- createdAt: string;
65
- aiModel?: string;
66
- tokenCost?: number;
67
- framework: FrameworkType;
68
- };
69
- }
70
- export interface GeneratorConfig {
71
- aiProvider: 'anthropic' | 'google' | 'openai';
72
- aiModel?: string;
73
- sourceRoot: string;
74
- uiModelPath: string;
75
- selectorPath: string;
76
- cachePath: string;
77
- framework: FrameworkType;
78
- enableCache: boolean;
79
- cacheExpiry?: number;
80
- parallelScans?: number;
81
- verbose?: boolean;
82
- }
83
- export interface DiscoverOptions {
84
- screenId?: string;
85
- all?: boolean;
86
- force?: boolean;
87
- }
88
- export interface GenerateOptions {
89
- screenId?: string;
90
- all?: boolean;
91
- force?: boolean;
92
- skipCache?: boolean;
93
- runtimeData?: boolean;
94
- }
95
- export interface ValidateOptions {
96
- screenId?: string;
97
- all?: boolean;
98
- executor?: ExecutorType;
99
- }
100
- export interface GherkinElementReference {
101
- elementId: string;
102
- screenId: string;
103
- elementName: string;
104
- }
105
- export declare function parseElementReference(ref: string): GherkinElementReference | null;
106
- export declare class SelectorNotFoundError extends Error {
107
- elementId: string;
108
- screenId: string;
109
- constructor(elementId: string, screenId: string);
110
- }
111
- export declare class UIModelNotFoundError extends Error {
112
- screenId: string;
113
- constructor(screenId: string);
114
- }
115
- export declare class GeneratorError extends Error {
116
- cause?: Error;
117
- constructor(message: string, cause?: Error);
118
- }
119
- //# sourceMappingURL=types.d.ts.map