@sun-asterisk/sungen 3.1.2-beta.102 → 3.1.2-beta.104

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.
@@ -69,6 +69,20 @@ A screen often matches several patterns at once — a login screen is *both* a f
69
69
  - VP-LOGIC = outcome depends on the user's *action* (click, submit, navigate)
70
70
  - VP-SEC = checks access control and malicious input
71
71
 
72
+ ### Domain category codes — required for the coverage-balance gate
73
+
74
+ The 4 viewpoints above are the *generic* axes. On a domain screen, the `VP-<CAT>` code must use the **canonical short code** for what the scenario exercises, so the audit's coverage-balance gate buckets it correctly. Use these exact codes — **never long-form or freeform** (`VP-NAV` not `VP-NAVIGATION`, `VP-SUB` not `VP-SUBSCRIPTION`, `VP-FILTER` not `VP-FILTERING`):
75
+
76
+ | Bucket | Codes | Use for |
77
+ |---|---|---|
78
+ | **business-core** | `LIST` · `CART` · `PRODUCT` · `FILTER` · `CHECKOUT` · `ORDER` | the screen's core domain data/actions (product list, cart, checkout, order, filtered results) |
79
+ | presentation | `UI` | layout / visual state |
80
+ | validation-security | `VAL` · `SEC` · `SUB` | input validation · access/injection · subscribe/newsletter |
81
+ | behavior | `LOGIC` | action-driven state changes |
82
+ | navigation | `NAV` | landing on / moving between pages |
83
+
84
+ **On a business-core page** (product list, cart, checkout, search results), the core data scenarios MUST carry a **business-core** code (`VP-LIST-*`, `VP-CART-*`, `VP-PRODUCT-*`, …) — not a generic `VP-UI`/`VP-LOGIC` or a freeform `VP-<word>`. A freeform/long-form prefix parses as `NONE`, scores **0 on the balance axis**, and drops the audit score (~9.3 → ~7.7 in practice). Keep `VP-UI/VAL/LOGIC/SEC` for the cross-cutting checks; give the domain scenarios their domain code.
85
+
72
86
  ---
73
87
 
74
88
  ## Shared Checks
@@ -69,6 +69,20 @@ A screen often matches several patterns at once — a login screen is *both* a f
69
69
  - VP-LOGIC = outcome depends on the user's *action* (click, submit, navigate)
70
70
  - VP-SEC = checks access control and malicious input
71
71
 
72
+ ### Domain category codes — required for the coverage-balance gate
73
+
74
+ The 4 viewpoints above are the *generic* axes. On a domain screen, the `VP-<CAT>` code must use the **canonical short code** for what the scenario exercises, so the audit's coverage-balance gate buckets it correctly. Use these exact codes — **never long-form or freeform** (`VP-NAV` not `VP-NAVIGATION`, `VP-SUB` not `VP-SUBSCRIPTION`, `VP-FILTER` not `VP-FILTERING`):
75
+
76
+ | Bucket | Codes | Use for |
77
+ |---|---|---|
78
+ | **business-core** | `LIST` · `CART` · `PRODUCT` · `FILTER` · `CHECKOUT` · `ORDER` | the screen's core domain data/actions (product list, cart, checkout, order, filtered results) |
79
+ | presentation | `UI` | layout / visual state |
80
+ | validation-security | `VAL` · `SEC` · `SUB` | input validation · access/injection · subscribe/newsletter |
81
+ | behavior | `LOGIC` | action-driven state changes |
82
+ | navigation | `NAV` | landing on / moving between pages |
83
+
84
+ **On a business-core page** (product list, cart, checkout, search results), the core data scenarios MUST carry a **business-core** code (`VP-LIST-*`, `VP-CART-*`, `VP-PRODUCT-*`, …) — not a generic `VP-UI`/`VP-LOGIC` or a freeform `VP-<word>`. A freeform/long-form prefix parses as `NONE`, scores **0 on the balance axis**, and drops the audit score (~9.3 → ~7.7 in practice). Keep `VP-UI/VAL/LOGIC/SEC` for the cross-cutting checks; give the domain scenarios their domain code.
85
+
72
86
  ---
73
87
 
74
88
  ## Shared Checks
@@ -1 +1 @@
1
- {"version":3,"file":"specs-api.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"AAkDA,cAAM,SAAS;IACb,OAAO,CAAC,OAAO,CAA8C;IAE7D,OAAO,CAAC,GAAG;IAWX,kHAAkH;IAC5G,IAAI,CACR,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EAC1E,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC/B,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CA6BxF;AAED,eAAO,MAAM,GAAG,WAAkB,CAAC"}
1
+ {"version":3,"file":"specs-api.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"AAmDA,cAAM,SAAS;IACb,OAAO,CAAC,OAAO,CAA8C;IAE7D,OAAO,CAAC,GAAG;IAWX,kHAAkH;IAC5G,IAAI,CACR,KAAK,EAAE,MAAM,EACb,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EAC1E,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC/B,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CA+BxF;AAED,eAAO,MAAM,GAAG,WAAkB,CAAC"}
@@ -48,6 +48,7 @@ exports.api = void 0;
48
48
  */
49
49
  const fs = __importStar(require("fs"));
50
50
  const path = __importStar(require("path"));
51
+ const test_1 = require("@playwright/test");
51
52
  function loadEnvQa() {
52
53
  for (const name of ['.env.qa', `.env.qa.${process.env.SUNGEN_ENV || ''}`]) {
53
54
  const p = path.join(process.cwd(), name);
@@ -95,33 +96,34 @@ class ApiClient {
95
96
  const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
96
97
  if (!base)
97
98
  throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
98
- const url = base + substitute(req.path, params);
99
- let body;
99
+ const urlPath = substitute(req.path, params); // path params (:id) bind at runtime
100
100
  const headers = { ...(conf.headers || {}) };
101
+ let data; // JSON body — Playwright serializes + sets content-type
101
102
  if (req.body !== undefined && req.body !== null) {
102
- const resolved = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
103
- body = JSON.stringify(resolved);
104
- if (!headers['content-type'] && !headers['Content-Type'])
105
- headers['content-type'] = 'application/json';
103
+ data = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
106
104
  }
107
- const controller = new AbortController();
108
- const timer = setTimeout(() => controller.abort(), conf.timeout_ms ?? 15000);
109
- let res;
105
+ // Playwright APIRequestContext: same runner/report/retries as UI tests, and can later be created
106
+ // from a browser context's storageState for @hybrid session-sharing. Disposed per call so no
107
+ // request context lingers and hangs the test process (cookie/connection reuse across a flow is a
108
+ // later optimization; flows pass auth explicitly via header/param).
109
+ const ctx = await test_1.request.newContext({
110
+ baseURL: base,
111
+ extraHTTPHeaders: headers,
112
+ timeout: conf.timeout_ms ?? 15000,
113
+ });
110
114
  try {
111
- res = await fetch(url, { method: req.method, headers, body, signal: controller.signal });
115
+ const res = await ctx.fetch(urlPath, { method: req.method, ...(data !== undefined ? { data } : {}) });
116
+ const text = await res.text();
117
+ let parsed = text;
118
+ try {
119
+ parsed = text ? JSON.parse(text) : null;
120
+ }
121
+ catch { /* non-JSON → keep text */ }
122
+ return { status: res.status(), ok: res.ok(), body: parsed, headers: res.headers() };
112
123
  }
113
124
  finally {
114
- clearTimeout(timer);
115
- }
116
- const text = await res.text();
117
- let parsed = text;
118
- try {
119
- parsed = text ? JSON.parse(text) : null;
125
+ await ctx.dispose();
120
126
  }
121
- catch { /* non-JSON → keep text */ }
122
- const outHeaders = {};
123
- res.headers.forEach((v, k) => { outHeaders[k] = v; });
124
- return { status: res.status, ok: res.ok, body: parsed, headers: outHeaders };
125
127
  }
126
128
  }
127
129
  exports.api = new ApiClient();
@@ -1 +1 @@
1
- {"version":3,"file":"specs-api.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specs-api.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-api.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
+ import { request, type APIRequestContext } from '@playwright/test';
15
16
 
16
17
  interface ApiDataSource {
17
18
  kind?: string;
@@ -71,30 +72,32 @@ class ApiClient {
71
72
  const { conf } = this.cfg(req.datasource);
72
73
  const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
73
74
  if (!base) throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
74
- const url = base + substitute(req.path, params);
75
+ const urlPath = substitute(req.path, params); // path params (:id) bind at runtime
75
76
 
76
- let body: string | undefined;
77
77
  const headers: Record<string, string> = { ...(conf.headers || {}) };
78
+ let data: any; // JSON body — Playwright serializes + sets content-type
78
79
  if (req.body !== undefined && req.body !== null) {
79
- const resolved = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
80
- body = JSON.stringify(resolved);
81
- if (!headers['content-type'] && !headers['Content-Type']) headers['content-type'] = 'application/json';
80
+ data = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
82
81
  }
83
82
 
84
- const controller = new AbortController();
85
- const timer = setTimeout(() => controller.abort(), conf.timeout_ms ?? 15000);
86
- let res: Response;
83
+ // Playwright APIRequestContext: same runner/report/retries as UI tests, and can later be created
84
+ // from a browser context's storageState for @hybrid session-sharing. Disposed per call so no
85
+ // request context lingers and hangs the test process (cookie/connection reuse across a flow is a
86
+ // later optimization; flows pass auth explicitly via header/param).
87
+ const ctx: APIRequestContext = await request.newContext({
88
+ baseURL: base,
89
+ extraHTTPHeaders: headers,
90
+ timeout: conf.timeout_ms ?? 15000,
91
+ });
87
92
  try {
88
- res = await fetch(url, { method: req.method, headers, body, signal: controller.signal });
93
+ const res = await ctx.fetch(urlPath, { method: req.method, ...(data !== undefined ? { data } : {}) });
94
+ const text = await res.text();
95
+ let parsed: any = text;
96
+ try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
97
+ return { status: res.status(), ok: res.ok(), body: parsed, headers: res.headers() };
89
98
  } finally {
90
- clearTimeout(timer);
99
+ await ctx.dispose();
91
100
  }
92
- const text = await res.text();
93
- let parsed: any = text;
94
- try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
95
- const outHeaders: Record<string, string> = {};
96
- res.headers.forEach((v, k) => { outHeaders[k] = v; });
97
- return { status: res.status, ok: res.ok, body: parsed, headers: outHeaders };
98
101
  }
99
102
  }
100
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "3.1.2-beta.102",
3
+ "version": "3.1.2-beta.104",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -33,7 +33,7 @@
33
33
  "node": ">=18.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@sungen/driver-ui": "3.1.2-beta.102",
36
+ "@sungen/driver-ui": "3.1.2-beta.104",
37
37
  "@anthropic-ai/sdk": "^0.71.0",
38
38
  "@babel/parser": "^7.28.5",
39
39
  "@babel/traverse": "^7.28.5",
@@ -69,6 +69,20 @@ A screen often matches several patterns at once — a login screen is *both* a f
69
69
  - VP-LOGIC = outcome depends on the user's *action* (click, submit, navigate)
70
70
  - VP-SEC = checks access control and malicious input
71
71
 
72
+ ### Domain category codes — required for the coverage-balance gate
73
+
74
+ The 4 viewpoints above are the *generic* axes. On a domain screen, the `VP-<CAT>` code must use the **canonical short code** for what the scenario exercises, so the audit's coverage-balance gate buckets it correctly. Use these exact codes — **never long-form or freeform** (`VP-NAV` not `VP-NAVIGATION`, `VP-SUB` not `VP-SUBSCRIPTION`, `VP-FILTER` not `VP-FILTERING`):
75
+
76
+ | Bucket | Codes | Use for |
77
+ |---|---|---|
78
+ | **business-core** | `LIST` · `CART` · `PRODUCT` · `FILTER` · `CHECKOUT` · `ORDER` | the screen's core domain data/actions (product list, cart, checkout, order, filtered results) |
79
+ | presentation | `UI` | layout / visual state |
80
+ | validation-security | `VAL` · `SEC` · `SUB` | input validation · access/injection · subscribe/newsletter |
81
+ | behavior | `LOGIC` | action-driven state changes |
82
+ | navigation | `NAV` | landing on / moving between pages |
83
+
84
+ **On a business-core page** (product list, cart, checkout, search results), the core data scenarios MUST carry a **business-core** code (`VP-LIST-*`, `VP-CART-*`, `VP-PRODUCT-*`, …) — not a generic `VP-UI`/`VP-LOGIC` or a freeform `VP-<word>`. A freeform/long-form prefix parses as `NONE`, scores **0 on the balance axis**, and drops the audit score (~9.3 → ~7.7 in practice). Keep `VP-UI/VAL/LOGIC/SEC` for the cross-cutting checks; give the domain scenarios their domain code.
85
+
72
86
  ---
73
87
 
74
88
  ## Shared Checks
@@ -69,6 +69,20 @@ A screen often matches several patterns at once — a login screen is *both* a f
69
69
  - VP-LOGIC = outcome depends on the user's *action* (click, submit, navigate)
70
70
  - VP-SEC = checks access control and malicious input
71
71
 
72
+ ### Domain category codes — required for the coverage-balance gate
73
+
74
+ The 4 viewpoints above are the *generic* axes. On a domain screen, the `VP-<CAT>` code must use the **canonical short code** for what the scenario exercises, so the audit's coverage-balance gate buckets it correctly. Use these exact codes — **never long-form or freeform** (`VP-NAV` not `VP-NAVIGATION`, `VP-SUB` not `VP-SUBSCRIPTION`, `VP-FILTER` not `VP-FILTERING`):
75
+
76
+ | Bucket | Codes | Use for |
77
+ |---|---|---|
78
+ | **business-core** | `LIST` · `CART` · `PRODUCT` · `FILTER` · `CHECKOUT` · `ORDER` | the screen's core domain data/actions (product list, cart, checkout, order, filtered results) |
79
+ | presentation | `UI` | layout / visual state |
80
+ | validation-security | `VAL` · `SEC` · `SUB` | input validation · access/injection · subscribe/newsletter |
81
+ | behavior | `LOGIC` | action-driven state changes |
82
+ | navigation | `NAV` | landing on / moving between pages |
83
+
84
+ **On a business-core page** (product list, cart, checkout, search results), the core data scenarios MUST carry a **business-core** code (`VP-LIST-*`, `VP-CART-*`, `VP-PRODUCT-*`, …) — not a generic `VP-UI`/`VP-LOGIC` or a freeform `VP-<word>`. A freeform/long-form prefix parses as `NONE`, scores **0 on the balance axis**, and drops the audit score (~9.3 → ~7.7 in practice). Keep `VP-UI/VAL/LOGIC/SEC` for the cross-cutting checks; give the domain scenarios their domain code.
85
+
72
86
  ---
73
87
 
74
88
  ## Shared Checks
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
+ import { request, type APIRequestContext } from '@playwright/test';
15
16
 
16
17
  interface ApiDataSource {
17
18
  kind?: string;
@@ -71,30 +72,32 @@ class ApiClient {
71
72
  const { conf } = this.cfg(req.datasource);
72
73
  const base = (conf.base_url || conf.baseUrl || '').replace(/\/$/, '');
73
74
  if (!base) throw new Error(`API Driver: ${label} — datasource has no base_url (set it in .env.qa).`);
74
- const url = base + substitute(req.path, params);
75
+ const urlPath = substitute(req.path, params); // path params (:id) bind at runtime
75
76
 
76
- let body: string | undefined;
77
77
  const headers: Record<string, string> = { ...(conf.headers || {}) };
78
+ let data: any; // JSON body — Playwright serializes + sets content-type
78
79
  if (req.body !== undefined && req.body !== null) {
79
- const resolved = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
80
- body = JSON.stringify(resolved);
81
- if (!headers['content-type'] && !headers['Content-Type']) headers['content-type'] = 'application/json';
80
+ data = JSON.parse(JSON.stringify(req.body).replace(/":([A-Za-z_][A-Za-z0-9_]*)"/g, (_m, p) => JSON.stringify(params[p] ?? null)));
82
81
  }
83
82
 
84
- const controller = new AbortController();
85
- const timer = setTimeout(() => controller.abort(), conf.timeout_ms ?? 15000);
86
- let res: Response;
83
+ // Playwright APIRequestContext: same runner/report/retries as UI tests, and can later be created
84
+ // from a browser context's storageState for @hybrid session-sharing. Disposed per call so no
85
+ // request context lingers and hangs the test process (cookie/connection reuse across a flow is a
86
+ // later optimization; flows pass auth explicitly via header/param).
87
+ const ctx: APIRequestContext = await request.newContext({
88
+ baseURL: base,
89
+ extraHTTPHeaders: headers,
90
+ timeout: conf.timeout_ms ?? 15000,
91
+ });
87
92
  try {
88
- res = await fetch(url, { method: req.method, headers, body, signal: controller.signal });
93
+ const res = await ctx.fetch(urlPath, { method: req.method, ...(data !== undefined ? { data } : {}) });
94
+ const text = await res.text();
95
+ let parsed: any = text;
96
+ try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
97
+ return { status: res.status(), ok: res.ok(), body: parsed, headers: res.headers() };
89
98
  } finally {
90
- clearTimeout(timer);
99
+ await ctx.dispose();
91
100
  }
92
- const text = await res.text();
93
- let parsed: any = text;
94
- try { parsed = text ? JSON.parse(text) : null; } catch { /* non-JSON → keep text */ }
95
- const outHeaders: Record<string, string> = {};
96
- res.headers.forEach((v, k) => { outHeaders[k] = v; });
97
- return { status: res.status, ok: res.ok, body: parsed, headers: outHeaders };
98
101
  }
99
102
  }
100
103