@sun-asterisk/sungen 3.2.6 → 3.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/delivery.d.ts +25 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +30 -47
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +28 -6
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +20 -20
- package/dist/dashboard/types.d.ts +18 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/api-catalog-loader.d.ts +18 -0
- package/dist/exporters/api-catalog-loader.d.ts.map +1 -0
- package/dist/exporters/api-catalog-loader.js +77 -0
- package/dist/exporters/api-catalog-loader.js.map +1 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +80 -4
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +6 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts +10 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +34 -2
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +14 -0
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +30 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/types.d.ts +5 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +18 -17
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +25 -595
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts +196 -0
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -0
- package/dist/exporters/xlsx-report-builder.js +653 -0
- package/dist/exporters/xlsx-report-builder.js.map +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +3 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +2 -2
- package/dist/generators/test-generator/step-mapper.d.ts +6 -2
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +10 -6
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +8 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +12 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +3 -0
- package/dist/orchestrator/templates/specs-api.d.ts +14 -2
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +38 -14
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +43 -14
- package/package.json +3 -3
- package/src/cli/commands/delivery.ts +32 -46
- package/src/dashboard/snapshot-builder.ts +31 -7
- package/src/dashboard/templates/index.html +20 -20
- package/src/dashboard/types.ts +19 -0
- package/src/exporters/api-catalog-loader.ts +44 -0
- package/src/exporters/api-testcase-formatter.ts +77 -4
- package/src/exporters/csv-exporter.ts +6 -1
- package/src/exporters/json-exporter.ts +46 -4
- package/src/exporters/playwright-report-parser.ts +27 -0
- package/src/exporters/types.ts +5 -0
- package/src/exporters/xlsx-exporter.ts +34 -658
- package/src/exporters/xlsx-report-builder.ts +806 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +3 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +2 -2
- package/src/generators/test-generator/step-mapper.ts +10 -6
- package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +8 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +12 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +3 -0
- package/src/orchestrator/templates/specs-api.ts +43 -14
- package/dist/cli/types.d.ts +0 -9
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/types.js +0 -7
- package/dist/cli/types.js.map +0 -1
- package/dist/generators/gherkin-parser/selector-extractor.d.ts +0 -37
- package/dist/generators/gherkin-parser/selector-extractor.d.ts.map +0 -1
- package/dist/generators/gherkin-parser/selector-extractor.js +0 -108
- package/dist/generators/gherkin-parser/selector-extractor.js.map +0 -1
- package/dist/generators/test-generator/types.d.ts +0 -25
- package/dist/generators/test-generator/types.d.ts.map +0 -1
- package/dist/generators/test-generator/types.js +0 -7
- package/dist/generators/test-generator/types.js.map +0 -1
- package/dist/generators/types.d.ts +0 -119
- package/dist/generators/types.d.ts.map +0 -1
- package/dist/generators/types.js +0 -48
- package/dist/generators/types.js.map +0 -1
- package/dist/orchestrator/figma/node-path-collapser.d.ts +0 -16
- package/dist/orchestrator/figma/node-path-collapser.d.ts.map +0 -1
- package/dist/orchestrator/figma/node-path-collapser.js +0 -37
- package/dist/orchestrator/figma/node-path-collapser.js.map +0 -1
- package/src/cli/types.ts +0 -9
- package/src/generators/gherkin-parser/selector-extractor.ts +0 -142
- package/src/generators/test-generator/types.ts +0 -26
- package/src/generators/types.ts +0 -227
- package/src/orchestrator/figma/node-path-collapser.ts +0 -38
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
await expect({{> locator}}.getByRole('columnheader', { name: '{{columnName}}' })).toBeVisible();
|
|
2
|
-
const {{columnIndexVar}} = (await page.getByRole('columnheader').allTextContents()).findIndex(h => h.includes('{{columnName}}'));
|
|
3
|
-
await expect(page.getByRole('row').nth({{rowNth}}).getByRole('cell').nth({{columnIndexVar}})).toHaveText('{{dataValue}}');
|
|
1
|
+
await expect({{> locator}}.getByRole('columnheader', { name: '{{columnName}}', includeHidden: true })).toBeVisible();
|
|
2
|
+
const {{columnIndexVar}} = (await page.getByRole('columnheader', { includeHidden: true }).filter({ visible: true }).allTextContents()).findIndex(h => h.includes('{{columnName}}'));
|
|
3
|
+
await expect(page.getByRole('row', { includeHidden: true }).filter({ visible: true }).nth({{rowNth}}).getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndexVar}})).toHaveText('{{dataValue}}');
|
package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect({{> locator}}.locator('tbody').getByRole('row')).toHaveCount(0);
|
|
1
|
+
await expect({{> locator}}.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true })).toHaveCount(0);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{{~#if isGiven~}}
|
|
2
2
|
{
|
|
3
|
-
const rows = {{> locator}}.locator('tbody').getByRole('row');
|
|
3
|
+
const rows = {{> locator}}.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true });
|
|
4
4
|
{{#each assertions}}
|
|
5
5
|
{{this}}
|
|
6
6
|
{{/each~}}
|
|
7
7
|
}
|
|
8
8
|
{{~else~}}
|
|
9
9
|
{
|
|
10
|
-
const rows = {{> locator}}.locator('tbody').getByRole('row');
|
|
10
|
+
const rows = {{> locator}}.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true });
|
|
11
11
|
{{#each assertions}}
|
|
12
12
|
{{this}}
|
|
13
13
|
{{/each~}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
await expect({{> locator}}.locator('tbody').getByRole('row')).toHaveCount({{expectedCount}});
|
|
1
|
+
await expect({{> locator}}.locator('tbody').getByRole('row', { includeHidden: true }).filter({ visible: true })).toHaveCount({{expectedCount}});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{{~#if isGiven}}
|
|
2
|
-
const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
|
|
2
|
+
const tableRow = {{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true });
|
|
3
3
|
await tableRow.waitFor();
|
|
4
4
|
{{~else}}
|
|
5
|
-
const tableRow = {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' });
|
|
5
|
+
const tableRow = {{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true });
|
|
6
6
|
await expect(tableRow).toBeVisible();
|
|
7
7
|
{{~/if}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{{~#if isGiven}}
|
|
2
|
-
await {{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' }).waitFor({ state: 'hidden' });
|
|
2
|
+
await {{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true }).waitFor({ state: 'hidden' });
|
|
3
3
|
{{~else}}
|
|
4
|
-
await expect({{> locator}}.getByRole('row').filter({ hasText: '{{escapeQuotes filterValue}}' })).toHaveCount(0);
|
|
4
|
+
await expect({{> locator}}.getByRole('row', { includeHidden: true }).filter({ hasText: '{{escapeQuotes filterValue}}' }).filter({ visible: true })).toHaveCount(0);
|
|
5
5
|
{{~/if}}
|
|
@@ -229,8 +229,12 @@ export class StepMapper {
|
|
|
229
229
|
* Generate column assertion scoped to the current row.
|
|
230
230
|
* Uses columns config for exact cell targeting (nth), or filter fallback.
|
|
231
231
|
*
|
|
232
|
-
* With columns: await expect(tableRow.getByRole('cell').nth(index)).toHaveText('value');
|
|
233
|
-
* Without: await expect(tableRow.getByRole('cell').filter({ hasText: 'value' })).toBeVisible();
|
|
232
|
+
* With columns: await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth(index)).toHaveText('value');
|
|
233
|
+
* Without: await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: 'value' }).filter({ visible: true })).toBeVisible();
|
|
234
|
+
*
|
|
235
|
+
* includeHidden + visible filter: when a modal (e.g. Radix Dialog) is open the
|
|
236
|
+
* background gets aria-hidden="true", which empties plain getByRole() even though
|
|
237
|
+
* the cells are still rendered — measure what the user SEES, not the a11y tree.
|
|
234
238
|
*/
|
|
235
239
|
private generateRowScopedColumnAssertion(step: ParsedStep): MappedStep {
|
|
236
240
|
const columnRef = step.selectorRef || '';
|
|
@@ -259,16 +263,16 @@ export class StepMapper {
|
|
|
259
263
|
if (columnIndex !== undefined) {
|
|
260
264
|
// Exact cell: nth(index) + toHaveText
|
|
261
265
|
if (isGiven) {
|
|
262
|
-
code = `await expect(tableRow.getByRole('cell').nth(${columnIndex})).toHaveText('${escapedValue}');`;
|
|
266
|
+
code = `await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth(${columnIndex})).toHaveText('${escapedValue}');`;
|
|
263
267
|
} else {
|
|
264
|
-
code = `await expect(tableRow.getByRole('cell').nth(${columnIndex})).toHaveText('${escapedValue}');`;
|
|
268
|
+
code = `await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth(${columnIndex})).toHaveText('${escapedValue}');`;
|
|
265
269
|
}
|
|
266
270
|
} else {
|
|
267
271
|
// Fallback: filter by text
|
|
268
272
|
if (isGiven) {
|
|
269
|
-
code = `await tableRow.getByRole('cell').filter({ hasText: '${escapedValue}' }).waitFor();`;
|
|
273
|
+
code = `await tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '${escapedValue}' }).filter({ visible: true }).waitFor();`;
|
|
270
274
|
} else {
|
|
271
|
-
code = `await expect(tableRow.getByRole('cell').filter({ hasText: '${escapedValue}' })).toBeVisible();`;
|
|
275
|
+
code = `await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '${escapedValue}' }).filter({ visible: true })).toBeVisible();`;
|
|
272
276
|
}
|
|
273
277
|
}
|
|
274
278
|
|
|
@@ -72,6 +72,14 @@ A flow (`create → login → delete`) is a **Functional integration** test, **n
|
|
|
72
72
|
## File upload (real files)
|
|
73
73
|
`body:` carries only scalar fields — to send a **real file**, declare it in the catalog entry; the runtime reads the fixture from disk. Two forms, by how the server accepts the file:
|
|
74
74
|
- **`files:`** → `multipart/form-data`, the file is a form part: `files: { file: { path: ":avatar", mimeType: image/png } }` (+ optional text `body:` fields alongside). Shorthand `files: { file: ":avatar" }`.
|
|
75
|
+
- **Multiple files under ONE field name** → make the field's value an **array** (each element a spec or shorthand string):
|
|
76
|
+
```yaml
|
|
77
|
+
files:
|
|
78
|
+
gift_image:
|
|
79
|
+
- ":gift_image_1" # test-data: gift_image_1 → fixtures/a.png
|
|
80
|
+
- ":gift_image_2" # test-data: gift_image_2 → fixtures/b.png
|
|
81
|
+
```
|
|
82
|
+
Each element binds its own `:param` from test-data. Use this for endpoints that accept a list of files under the same field — a single object value can only hold one file. **Automate multi-file uploads with `@api`; don't defer to `@manual`.** (File uploads use Playwright's `FormData` multipart, which requires **`@playwright/test` ≥ 1.44** — the version sungen installs by default; only projects pinned to an older Playwright need to upgrade.)
|
|
75
83
|
- **`bodyFile:`** → raw binary body (the whole body IS the file's bytes, e.g. `application/octet-stream`): `bodyFile: { path: ":image", mimeType: application/octet-stream }`.
|
|
76
84
|
|
|
77
85
|
Fixture path resolves cwd-relative/absolute first, else `qa/fixtures/<path>` (drop sample files there, reference by name from `test-data`). An empty resolved file param omits the part → use for missing-file `@cases` error rows. `files:`/`bodyFile:` are mutually exclusive. **Automate the upload success case with `@api`** — don't defer it to `@manual`.
|
|
@@ -91,7 +91,7 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
91
91
|
| toHaveValue mismatch | Expected value differs from actual | Fix value in test-data |
|
|
92
92
|
| toContainText mismatch | Partial text not found | Fix expected partial text in test-data |
|
|
93
93
|
| toBeVisible timeout | Element exists but hidden, or name wrong | Check: is element conditionally visible? Wrong name? Inside dialog? |
|
|
94
|
-
| toHaveCount mismatch | Row count differs | Fix expected count in test-data. Verify: is table loaded? Filtered? |
|
|
94
|
+
| toHaveCount mismatch | Row count differs | Fix expected count in test-data. Verify: is table loaded? Filtered? If `Received: 0` while a dialog is open → aria-hidden trap, see **Table-Specific Errors** |
|
|
95
95
|
|
|
96
96
|
### Assertion type rule
|
|
97
97
|
|
|
@@ -111,6 +111,7 @@ If `toHaveText` fails on an input → the Gherkin step has the wrong target type
|
|
|
111
111
|
| `tableRow is not defined` | Column assertion without preceding row scope step | The Gherkin is missing a row-scope step — this is a **QA authoring gap → report it, let it FAIL**. Do NOT add the step during run-test (the Gherkin is frozen). |
|
|
112
112
|
| `toHaveText` on cell fails (with columns) | Wrong column index in `columns` config | Re-count columns in snapshot (0-indexed). Fix `index` in selectors.yaml |
|
|
113
113
|
| `toBeVisible` on cell fails (no columns) | `filter({ hasText })` didn't match | Check exact cell text in snapshot. Fix value in test-data |
|
|
114
|
+
| Row/cell count reads 0 while a dialog is open, rows visibly present behind it | **aria-hidden trap**: the modal (Radix/MUI/HeadlessUI…) sets `aria-hidden="true"` on the background; plain `getByRole('row'/'cell')` reads the a11y tree, which is empty behind a modal. Twin symptom: count-0 assertions (`table is empty`, `row not exists`) false-PASS under the same condition | Re-run `sungen generate` — table locators from sungen ≥3.2.8 emit `getByRole(…, { includeHidden: true }).filter({ visible: true })` and are immune. Do NOT edit the expected count or weaken the assertion — the rows exist; the old locator measured the wrong tree |
|
|
114
115
|
| Row filter matches 0 rows | Filter text doesn't match any row content | Re-snapshot → find actual row text. Fix filter value in test-data |
|
|
115
116
|
| Row filter matches multiple rows | Filter text is too generic (matches multiple rows) | Use more specific filter text (unique identifier like email, ID) |
|
|
116
117
|
| Table not found | Wrong table name or table not rendered | Re-snapshot → copy exact table accessible name |
|
|
@@ -153,6 +154,16 @@ If `toHaveText` fails on an input → the Gherkin step has the wrong target type
|
|
|
153
154
|
|
|
154
155
|
---
|
|
155
156
|
|
|
157
|
+
## API Request Errors (`@api` / catalog)
|
|
158
|
+
|
|
159
|
+
| Error | Diagnosis | Fix |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `400 "Unexpected field"` on a file upload | Endpoint accepts **multiple files under one field**, but the catalog declared a single file (one part sent) | In `apis.yaml`, make that `files:` field an **array** — one `:param` per file: `files: { gift_image: [":gift_image_1", ":gift_image_2"] }`. Add matching `test-data` paths. This is a real automatable case — do NOT defer to `@manual`. |
|
|
162
|
+
| `415 Unsupported Media Type` on upload | Sent multipart where the endpoint wants a raw binary body (or vice-versa) | Use `bodyFile:` (raw binary body) instead of `files:` (multipart part), or the reverse — they are mutually exclusive. |
|
|
163
|
+
| `fixture "…" not found` | The referenced file isn't on disk | Drop the sample file under `qa/fixtures/` and reference it by name from `test-data`. |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
156
167
|
## Performance & Infrastructure Errors → Fix in `specs/base.ts`
|
|
157
168
|
|
|
158
169
|
All generated `.spec.ts` import from `specs/base.ts` — shared context caching, navigation, overlay cleanup. AI **can and should** tune `base.ts` to match the project.
|
|
@@ -282,9 +282,12 @@ Report results. Do NOT enter another fix loop here.
|
|
|
282
282
|
| `toHaveText` / `toHaveValue` mismatch | Wrong expected data | `test-data.yaml` |
|
|
283
283
|
| `page.goto` error | Wrong URL | page selector in `selectors.yaml` |
|
|
284
284
|
| `frame` error | Element inside iframe | add `frame` field |
|
|
285
|
+
| `role` selector resolves to 0 elements **while a dialog is open**, element visibly rendered behind it | aria-hidden trap — the modal sets `aria-hidden="true"` on the background; role locators read the a11y tree | see aria-hidden note below — NOT a name mismatch, don't churn the selector name |
|
|
285
286
|
|
|
286
287
|
**Group by root cause** — if 5 tests fail because `[Submit]` button has a different name, that's 1 fix, not 5.
|
|
287
288
|
|
|
289
|
+
> **aria-hidden trap** (element exists in the screenshot, locator says 0, a modal is open): compiled TABLE steps are immune from sungen ≥3.2.8 (`includeHidden` + `visible` filter) — if the failing generated code lacks that, re-run `sungen generate` first. For a **user-defined `role` selector** asserted while a dialog is open, the selector name is NOT the problem: prefer restructuring the scenario to assert after the dialog closes; if asserting behind the modal is genuinely intended, switch that selector to `testid` or an allowed `locator` CSS (both DOM-based, immune to aria-hidden). Never "fix" it by renaming the role.
|
|
290
|
+
|
|
288
291
|
**Check `test-results/` first** — Playwright captures failure screenshots automatically. Use these to diagnose before any MCP exploration.
|
|
289
292
|
|
|
290
293
|
### Step 2: Targeted MCP Exploration
|
|
@@ -80,6 +80,46 @@ export function resolveFixture(p: string, label: string): string {
|
|
|
80
80
|
throw new Error(`API Driver: ${label} — fixture "${p}" not found (looked in ${direct} and qa/fixtures/${p}).`);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Build the multipart FormData body for a file-upload request. A field's value may be a single spec
|
|
85
|
+
* OR an array of specs — an array sends MULTIPLE files under the SAME field name, which a plain object
|
|
86
|
+
* cannot express (a repeated key would overwrite). FormData.append keeps every part, so the request
|
|
87
|
+
* carries all files. Text `body` values ride along as form fields. An empty resolved path omits that
|
|
88
|
+
* part (the missing-file / wrong-type error row). Exported for unit test (assert form.getAll entries).
|
|
89
|
+
*
|
|
90
|
+
* Blob (not File): the global File constructor only exists on Node ≥20 while this package supports
|
|
91
|
+
* Node ≥18; Blob is available on 18+. FormData wraps Blob + filename into a File part internally, so
|
|
92
|
+
* the wire result is identical.
|
|
93
|
+
*/
|
|
94
|
+
export function buildMultipart(
|
|
95
|
+
files: Record<string, FileSpec | FileSpec[]>,
|
|
96
|
+
body: unknown,
|
|
97
|
+
params: Record<string, any>,
|
|
98
|
+
label: string,
|
|
99
|
+
): FormData {
|
|
100
|
+
const form = new FormData();
|
|
101
|
+
// Append file parts first. A field's value may be a single spec or an array (multiple files under
|
|
102
|
+
// one name); FormData.append keeps every part where a plain object would overwrite on a repeated key.
|
|
103
|
+
for (const [field, raw] of Object.entries(files)) {
|
|
104
|
+
for (const s of Array.isArray(raw) ? raw : [raw]) {
|
|
105
|
+
const spec = typeof s === 'string' ? { path: s } : s;
|
|
106
|
+
const fpath = substituteRaw(String(spec.path), params);
|
|
107
|
+
if (!fpath) continue; // empty param → omit the part (a missing-file / wrong-type error row)
|
|
108
|
+
const resolved = resolveFixture(fpath, label);
|
|
109
|
+
const filename = spec.filename ?? path.basename(resolved);
|
|
110
|
+
const blob = new Blob([fs.readFileSync(resolved)], { type: spec.mimeType ?? inferMime(filename) });
|
|
111
|
+
form.append(spec.name ?? field, blob, filename);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Text body fields ride along — but skip any name already added as a file part, so a catalog that
|
|
115
|
+
// declares the same key in both body: and files: sends ONE part (the file wins), matching the prior
|
|
116
|
+
// plain-object behaviour rather than emitting a duplicate field a strict server would reject.
|
|
117
|
+
if (body && typeof body === 'object')
|
|
118
|
+
for (const [k, v] of Object.entries(body as Record<string, unknown>))
|
|
119
|
+
if (!form.has(k)) form.append(k, typeof v === 'string' ? v : JSON.stringify(v));
|
|
120
|
+
return form;
|
|
121
|
+
}
|
|
122
|
+
|
|
83
123
|
/**
|
|
84
124
|
* Join a datasource base URL with a catalog path. Concatenate rather than rely on Playwright's
|
|
85
125
|
* baseURL resolution: an absolute path (`/user/1`) resolves against the base ORIGIN and would drop
|
|
@@ -113,7 +153,7 @@ class ApiClient {
|
|
|
113
153
|
*/
|
|
114
154
|
async call(
|
|
115
155
|
label: 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 },
|
|
156
|
+
req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; files?: Record<string, FileSpec | FileSpec[]>; bodyFile?: FileSpec; headers?: Record<string, string>; timeout?: number; datasource?: string },
|
|
117
157
|
params: Record<string, any> = {},
|
|
118
158
|
opts: { storageState?: string } = {},
|
|
119
159
|
): Promise<{ status: number; ok: boolean; body: any; headers: Record<string, string> }> {
|
|
@@ -150,21 +190,10 @@ class ApiClient {
|
|
|
150
190
|
headers['content-type'] = spec.mimeType ?? 'application/octet-stream';
|
|
151
191
|
}
|
|
152
192
|
} 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
193
|
// Playwright generates the multipart/form-data Content-Type (with boundary); drop any datasource
|
|
165
194
|
// default Content-Type so it doesn't conflict with / duplicate the generated one.
|
|
166
195
|
deleteHeader(headers, 'content-type');
|
|
167
|
-
bodyOpt.multipart =
|
|
196
|
+
bodyOpt.multipart = buildMultipart(req.files, body, params, label);
|
|
168
197
|
} else if (body !== undefined) {
|
|
169
198
|
const enc = req.encoding ?? 'json';
|
|
170
199
|
if (enc === 'form') bodyOpt.form = body;
|
|
@@ -201,7 +230,7 @@ class ApiClient {
|
|
|
201
230
|
*/
|
|
202
231
|
async callN(
|
|
203
232
|
label: 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 },
|
|
233
|
+
req: { method: string; path: string; body?: unknown; encoding?: 'json' | 'form' | 'multipart'; files?: Record<string, FileSpec | FileSpec[]>; bodyFile?: FileSpec; headers?: Record<string, string>; timeout?: number; datasource?: string },
|
|
205
234
|
params: Record<string, any> = {},
|
|
206
235
|
n = 1,
|
|
207
236
|
opts: { storageState?: string } = {},
|
package/dist/cli/types.d.ts
DELETED
|
@@ -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
|
package/dist/cli/types.d.ts.map
DELETED
|
@@ -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
package/dist/cli/types.js.map
DELETED
|
@@ -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":"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"}
|
|
@@ -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 +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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/generators/types.ts"],"names":[],"mappings":"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"}
|