@sun-asterisk/sungen 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +15 -0
  3. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  4. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -0
  5. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
  6. package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
  7. package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +2 -1
  8. package/dist/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  9. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  10. package/dist/generators/test-generator/code-generator.d.ts +1 -0
  11. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  12. package/dist/generators/test-generator/code-generator.js +76 -6
  13. package/dist/generators/test-generator/code-generator.js.map +1 -1
  14. package/dist/generators/test-generator/patterns/interaction-patterns.d.ts.map +1 -1
  15. package/dist/generators/test-generator/patterns/interaction-patterns.js +22 -3
  16. package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
  17. package/dist/generators/test-generator/patterns/navigation-patterns.d.ts.map +1 -1
  18. package/dist/generators/test-generator/patterns/navigation-patterns.js +8 -3
  19. package/dist/generators/test-generator/patterns/navigation-patterns.js.map +1 -1
  20. package/dist/generators/test-generator/template-engine.d.ts +13 -0
  21. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  22. package/dist/generators/test-generator/template-engine.js +1 -1
  23. package/dist/generators/test-generator/template-engine.js.map +1 -1
  24. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  25. package/dist/orchestrator/screen-manager.js +3 -1
  26. package/dist/orchestrator/screen-manager.js.map +1 -1
  27. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +70 -10
  28. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +23 -0
  29. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +70 -10
  30. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +23 -0
  31. package/dist/orchestrator/templates/specs-base.d.ts +3 -4
  32. package/dist/orchestrator/templates/specs-base.d.ts.map +1 -1
  33. package/dist/orchestrator/templates/specs-base.js +53 -39
  34. package/dist/orchestrator/templates/specs-base.js.map +1 -1
  35. package/dist/orchestrator/templates/specs-base.ts +55 -45
  36. package/dist/orchestrator/templates/specs-test-data.d.ts.map +1 -1
  37. package/dist/orchestrator/templates/specs-test-data.js +43 -0
  38. package/dist/orchestrator/templates/specs-test-data.js.map +1 -1
  39. package/dist/orchestrator/templates/specs-test-data.ts +47 -0
  40. package/package.json +1 -1
  41. package/src/cli/index.ts +1 -1
  42. package/src/generators/test-generator/adapters/adapter-interface.ts +6 -1
  43. package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +1 -1
  44. package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +2 -1
  45. package/src/generators/test-generator/adapters/playwright/templates/scenario.hbs +20 -1
  46. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +84 -4
  47. package/src/generators/test-generator/code-generator.ts +88 -7
  48. package/src/generators/test-generator/patterns/interaction-patterns.ts +25 -3
  49. package/src/generators/test-generator/patterns/navigation-patterns.ts +8 -3
  50. package/src/generators/test-generator/template-engine.ts +5 -2
  51. package/src/orchestrator/screen-manager.ts +3 -1
  52. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +70 -10
  53. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +23 -0
  54. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +70 -10
  55. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +23 -0
  56. package/src/orchestrator/templates/specs-base.ts +55 -45
  57. package/src/orchestrator/templates/specs-test-data.ts +47 -0
@@ -1,10 +1,62 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.expect = exports.test = void 0;
4
+ exports.cleanupPage = cleanupPage;
4
5
  const test_1 = require("@playwright/test");
5
6
  Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_1.expect; } });
7
+ async function cleanupPage(page, config) {
8
+ if (config.overlay) {
9
+ await page.keyboard.press('Escape').catch(() => { });
10
+ await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => { });
11
+ const hasOverlay = await page.evaluate(`(() => {
12
+ const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
13
+ if (!el) return false;
14
+ let current = el;
15
+ while (current && current !== document.body) {
16
+ if (getComputedStyle(current).position === 'fixed') return true;
17
+ current = current.parentElement;
18
+ }
19
+ return false;
20
+ })()`).catch(() => false);
21
+ if (hasOverlay) {
22
+ await page.keyboard.press('Escape').catch(() => { });
23
+ }
24
+ }
25
+ if (config.forms) {
26
+ await page.evaluate(`(() => {
27
+ const inputSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
28
+ const textareaSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
29
+ document.querySelectorAll('input:not([type=hidden]):not([type=submit]):not([type=checkbox]):not([type=radio])').forEach(el => {
30
+ inputSetter?.call(el, '');
31
+ el.dispatchEvent(new Event('input', { bubbles: true }));
32
+ el.dispatchEvent(new Event('change', { bubbles: true }));
33
+ });
34
+ document.querySelectorAll('textarea').forEach(el => {
35
+ textareaSetter?.call(el, '');
36
+ el.dispatchEvent(new Event('input', { bubbles: true }));
37
+ el.dispatchEvent(new Event('change', { bubbles: true }));
38
+ });
39
+ document.querySelectorAll('select').forEach(el => {
40
+ el.selectedIndex = 0;
41
+ el.dispatchEvent(new Event('change', { bubbles: true }));
42
+ });
43
+ document.querySelectorAll('input[type=checkbox]').forEach(el => {
44
+ if (el.checked !== el.defaultChecked) {
45
+ el.checked = el.defaultChecked;
46
+ el.dispatchEvent(new Event('change', { bubbles: true }));
47
+ }
48
+ });
49
+ document.querySelectorAll('form').forEach(f => f.reset());
50
+ })()`).catch(() => { });
51
+ }
52
+ if (config.scroll) {
53
+ await page.evaluate('window.scrollTo(0, 0)').catch(() => { });
54
+ }
55
+ if (config.storage) {
56
+ await page.evaluate('sessionStorage.clear()').catch(() => { });
57
+ }
58
+ }
6
59
  const test = test_1.test.extend({
7
- autoCleanup: [{}, { option: true }],
8
60
  screenshotOnFailure: [false, { option: true }],
9
61
  page: async ({ browser, storageState }, use) => {
10
62
  const context = storageState
@@ -15,44 +67,6 @@ const test = test_1.test.extend({
15
67
  await page.close();
16
68
  await context.close();
17
69
  },
18
- // Auto-cleanup fixture: runs teardown after each test based on @cleanup:* tags
19
- _autoCleanup: [async ({ page, autoCleanup }, use, testInfo) => {
20
- await use();
21
- // Only run cleanup when the test failed — avoids masking state for the next test
22
- if (testInfo.status === testInfo.expectedStatus)
23
- return;
24
- if (autoCleanup.overlay) {
25
- await page.keyboard.press('Escape').catch(() => { });
26
- await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => { });
27
- const hasOverlay = await page.evaluate(`(() => {
28
- const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
29
- if (!el) return false;
30
- let current = el;
31
- while (current && current !== document.body) {
32
- if (getComputedStyle(current).position === 'fixed') return true;
33
- current = current.parentElement;
34
- }
35
- return false;
36
- })()`).catch(() => false);
37
- if (hasOverlay) {
38
- await page.keyboard.press('Escape').catch(() => { });
39
- }
40
- }
41
- if (autoCleanup.forms) {
42
- await page.evaluate(`(() => {
43
- document.querySelectorAll('input:not([type=hidden]):not([type=submit])').forEach(el => { el.value = ''; });
44
- document.querySelectorAll('textarea').forEach(el => { el.value = ''; });
45
- document.querySelectorAll('select').forEach(el => { el.selectedIndex = 0; });
46
- })()`).catch(() => { });
47
- }
48
- if (autoCleanup.scroll) {
49
- await page.evaluate('window.scrollTo(0, 0)').catch(() => { });
50
- }
51
- if (autoCleanup.storage) {
52
- await page.evaluate('sessionStorage.clear()').catch(() => { });
53
- }
54
- }, { auto: true }],
55
- // Auto-screenshot fixture: captures screenshot on test failure
56
70
  _autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
57
71
  await use();
58
72
  if (screenshotOnFailure && testInfo.status !== testInfo.expectedStatus) {
@@ -1 +1 @@
1
- {"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specs-base.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-base.ts"],"names":[],"mappings":";;;AA0FuB,kCAAW;AA1FlC,2CAAmE;AA0FpD,uFA1FQ,aAAM,OA0FR;AAjFrB,KAAK,UAAU,WAAW,CAAC,IAAU,EAAE,MAAqB;IAC1D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACpD,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC5F,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;SASlC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;SAwBf,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,IAAI,GAAG,WAAI,CAAC,MAAM,CAGrB;IACD,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAE9C,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,EAAE;QAC7C,MAAM,OAAO,GAAG,YAAY;YAC1B,CAAC,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;YAC5C,CAAC,CAAC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE;YACvE,MAAM,GAAG,EAAE,CAAC;YAEZ,IAAI,mBAAmB,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,cAAc,EAAE,CAAC;gBACvE,MAAM,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE;oBAClC,IAAI,EAAE,MAAM,IAAI,CAAC,UAAU,EAAE;oBAC7B,WAAW,EAAE,WAAW;iBACzB,CAAC,CAAC;YACL,CAAC;QACH,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;CACnB,CAAC,CAAC;AAEM,oBAAI"}
@@ -1,4 +1,4 @@
1
- import { test as base, expect } from '@playwright/test';
1
+ import { test as base, expect, type Page } from '@playwright/test';
2
2
 
3
3
  type CleanupConfig = {
4
4
  overlay?: boolean;
@@ -7,13 +7,63 @@ type CleanupConfig = {
7
7
  storage?: boolean;
8
8
  };
9
9
 
10
+ async function cleanupPage(page: Page, config: CleanupConfig): Promise<void> {
11
+ if (config.overlay) {
12
+ await page.keyboard.press('Escape').catch(() => {});
13
+ await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
14
+ const hasOverlay = await page.evaluate(`(() => {
15
+ const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
16
+ if (!el) return false;
17
+ let current = el;
18
+ while (current && current !== document.body) {
19
+ if (getComputedStyle(current).position === 'fixed') return true;
20
+ current = current.parentElement;
21
+ }
22
+ return false;
23
+ })()`).catch(() => false);
24
+ if (hasOverlay) {
25
+ await page.keyboard.press('Escape').catch(() => {});
26
+ }
27
+ }
28
+ if (config.forms) {
29
+ await page.evaluate(`(() => {
30
+ const inputSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
31
+ const textareaSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
32
+ document.querySelectorAll('input:not([type=hidden]):not([type=submit]):not([type=checkbox]):not([type=radio])').forEach(el => {
33
+ inputSetter?.call(el, '');
34
+ el.dispatchEvent(new Event('input', { bubbles: true }));
35
+ el.dispatchEvent(new Event('change', { bubbles: true }));
36
+ });
37
+ document.querySelectorAll('textarea').forEach(el => {
38
+ textareaSetter?.call(el, '');
39
+ el.dispatchEvent(new Event('input', { bubbles: true }));
40
+ el.dispatchEvent(new Event('change', { bubbles: true }));
41
+ });
42
+ document.querySelectorAll('select').forEach(el => {
43
+ el.selectedIndex = 0;
44
+ el.dispatchEvent(new Event('change', { bubbles: true }));
45
+ });
46
+ document.querySelectorAll('input[type=checkbox]').forEach(el => {
47
+ if (el.checked !== el.defaultChecked) {
48
+ el.checked = el.defaultChecked;
49
+ el.dispatchEvent(new Event('change', { bubbles: true }));
50
+ }
51
+ });
52
+ document.querySelectorAll('form').forEach(f => f.reset());
53
+ })()`).catch(() => {});
54
+ }
55
+ if (config.scroll) {
56
+ await page.evaluate('window.scrollTo(0, 0)').catch(() => {});
57
+ }
58
+ if (config.storage) {
59
+ await page.evaluate('sessionStorage.clear()').catch(() => {});
60
+ }
61
+ }
62
+
10
63
  const test = base.extend<{
11
- autoCleanup: CleanupConfig;
12
64
  screenshotOnFailure: boolean;
13
- _autoCleanup: void;
14
65
  _autoScreenshot: void;
15
66
  }>({
16
- autoCleanup: [{}, { option: true }],
17
67
  screenshotOnFailure: [false, { option: true }],
18
68
 
19
69
  page: async ({ browser, storageState }, use) => {
@@ -26,46 +76,6 @@ const test = base.extend<{
26
76
  await context.close();
27
77
  },
28
78
 
29
- // Auto-cleanup fixture: runs teardown after each test based on @cleanup:* tags
30
- _autoCleanup: [async ({ page, autoCleanup }, use, testInfo) => {
31
- await use();
32
-
33
- // Only run cleanup when the test failed — avoids masking state for the next test
34
- if (testInfo.status === testInfo.expectedStatus) return;
35
-
36
- if (autoCleanup.overlay) {
37
- await page.keyboard.press('Escape').catch(() => {});
38
- await page.locator('body').click({ position: { x: 1, y: 1 }, force: true }).catch(() => {});
39
- const hasOverlay = await page.evaluate(`(() => {
40
- const el = document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2);
41
- if (!el) return false;
42
- let current = el;
43
- while (current && current !== document.body) {
44
- if (getComputedStyle(current).position === 'fixed') return true;
45
- current = current.parentElement;
46
- }
47
- return false;
48
- })()`).catch(() => false);
49
- if (hasOverlay) {
50
- await page.keyboard.press('Escape').catch(() => {});
51
- }
52
- }
53
- if (autoCleanup.forms) {
54
- await page.evaluate(`(() => {
55
- document.querySelectorAll('input:not([type=hidden]):not([type=submit])').forEach(el => { el.value = ''; });
56
- document.querySelectorAll('textarea').forEach(el => { el.value = ''; });
57
- document.querySelectorAll('select').forEach(el => { el.selectedIndex = 0; });
58
- })()`).catch(() => {});
59
- }
60
- if (autoCleanup.scroll) {
61
- await page.evaluate('window.scrollTo(0, 0)').catch(() => {});
62
- }
63
- if (autoCleanup.storage) {
64
- await page.evaluate('sessionStorage.clear()').catch(() => {});
65
- }
66
- }, { auto: true }],
67
-
68
- // Auto-screenshot fixture: captures screenshot on test failure
69
79
  _autoScreenshot: [async ({ page, screenshotOnFailure }, use, testInfo) => {
70
80
  await use();
71
81
 
@@ -78,4 +88,4 @@ const test = base.extend<{
78
88
  }, { auto: true }],
79
89
  });
80
90
 
81
- export { test, expect };
91
+ export { test, expect, cleanupPage };
@@ -1 +1 @@
1
- {"version":3,"file":"specs-test-data.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"AAIA,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAsB;IAElC,OAAO;IAIP;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc;IAmBpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAczB"}
1
+ {"version":3,"file":"specs-test-data.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"AAKA,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAsB;IAElC,OAAO;IAIP;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc;IAqBpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;CAczB"}
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.TestDataLoader = void 0;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
+ const crypto = __importStar(require("crypto"));
42
43
  const yaml_1 = __importDefault(require("yaml"));
43
44
  class TestDataLoader {
44
45
  constructor(data) {
@@ -68,6 +69,7 @@ class TestDataLoader {
68
69
  if (envData)
69
70
  data = deepMerge(data, envData);
70
71
  }
72
+ data = resolveDynamicVars(data);
71
73
  return new TestDataLoader(data);
72
74
  }
73
75
  get(key) {
@@ -105,4 +107,45 @@ function deepMerge(base, override) {
105
107
  }
106
108
  return result;
107
109
  }
110
+ function resolveDynamicVars(data) {
111
+ const ts = String(Date.now());
112
+ const uid = crypto.randomUUID();
113
+ const now = new Date();
114
+ const date = now.toISOString().split('T')[0];
115
+ const datetime = now.toISOString();
116
+ function resolveValue(value) {
117
+ if (typeof value === 'string') {
118
+ return value.replace(/\{\{\$(\w+)(?::([^}]*))?\}\}/g, (match, name, args) => {
119
+ switch (name) {
120
+ case 'timestamp':
121
+ return ts;
122
+ case 'uuid':
123
+ return uid;
124
+ case 'random': {
125
+ const [min, max] = (args || '1:9999').split(':').map(Number);
126
+ return String(Math.floor(Math.random() * (max - min + 1)) + min);
127
+ }
128
+ case 'date':
129
+ return date;
130
+ case 'datetime':
131
+ return datetime;
132
+ default:
133
+ return match;
134
+ }
135
+ });
136
+ }
137
+ if (Array.isArray(value)) {
138
+ return value.map(resolveValue);
139
+ }
140
+ if (value && typeof value === 'object') {
141
+ const resolved = {};
142
+ for (const [k, v] of Object.entries(value)) {
143
+ resolved[k] = resolveValue(v);
144
+ }
145
+ return resolved;
146
+ }
147
+ return value;
148
+ }
149
+ return resolveValue(data);
150
+ }
108
151
  //# sourceMappingURL=specs-test-data.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specs-test-data.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"specs-test-data.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-test-data.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import * as crypto from 'crypto';
3
4
  import yaml from 'yaml';
4
5
 
5
6
  export class TestDataLoader {
@@ -34,6 +35,8 @@ export class TestDataLoader {
34
35
  if (envData) data = deepMerge(data, envData);
35
36
  }
36
37
 
38
+ data = resolveDynamicVars(data);
39
+
37
40
  return new TestDataLoader(data);
38
41
  }
39
42
 
@@ -71,3 +74,47 @@ function deepMerge(base: Record<string, any>, override: Record<string, any>): Re
71
74
  }
72
75
  return result;
73
76
  }
77
+
78
+ function resolveDynamicVars(data: Record<string, any>): Record<string, any> {
79
+ const ts = String(Date.now());
80
+ const uid = crypto.randomUUID();
81
+ const now = new Date();
82
+ const date = now.toISOString().split('T')[0];
83
+ const datetime = now.toISOString();
84
+
85
+ function resolveValue(value: any): any {
86
+ if (typeof value === 'string') {
87
+ return value.replace(/\{\{\$(\w+)(?::([^}]*))?\}\}/g, (match, name, args) => {
88
+ switch (name) {
89
+ case 'timestamp':
90
+ return ts;
91
+ case 'uuid':
92
+ return uid;
93
+ case 'random': {
94
+ const [min, max] = (args || '1:9999').split(':').map(Number);
95
+ return String(Math.floor(Math.random() * (max - min + 1)) + min);
96
+ }
97
+ case 'date':
98
+ return date;
99
+ case 'datetime':
100
+ return datetime;
101
+ default:
102
+ return match;
103
+ }
104
+ });
105
+ }
106
+ if (Array.isArray(value)) {
107
+ return value.map(resolveValue);
108
+ }
109
+ if (value && typeof value === 'object') {
110
+ const resolved: Record<string, any> = {};
111
+ for (const [k, v] of Object.entries(value)) {
112
+ resolved[k] = resolveValue(v);
113
+ }
114
+ return resolved;
115
+ }
116
+ return value;
117
+ }
118
+
119
+ return resolveValue(data);
120
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/cli/index.ts CHANGED
@@ -20,7 +20,7 @@ async function main() {
20
20
  program
21
21
  .name('sungen')
22
22
  .description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
23
- .version('2.6.0');
23
+ .version('2.6.1');
24
24
 
25
25
  // Global options
26
26
  program
@@ -21,6 +21,9 @@ export interface TestFileData {
21
21
  runtimeData?: boolean; // --runtime-data flag: testData.get() instead of hardcoded values
22
22
  screenName?: string; // Screen name for TestDataLoader.load()
23
23
  featureFileName?: string; // Feature file name for TestDataLoader.load()
24
+ isParallel?: boolean; // @parallel tag: fresh page per test (opt-out from serial default)
25
+ cleanup?: { overlay?: boolean; forms?: boolean; scroll?: boolean; storage?: boolean };
26
+ backgroundSteps?: Array<{ comment?: string; code: string }>; // Raw background steps for serial beforeAll
24
27
  scenarios: string[];
25
28
  authGroups?: AuthGroup[]; // Grouped by auth role for nested describes
26
29
  singleAuthRole?: string; // Auth role when all scenarios share the same role
@@ -30,6 +33,8 @@ export interface ScenarioData {
30
33
  scenarioName: string;
31
34
  steps: Array<{ comment?: string; code: string }>;
32
35
  authRole?: string; // Auth role for storage state
36
+ isParallel?: boolean; // @parallel: use fresh page from fixture
37
+ tags?: string; // Pass-through tags for Playwright { tag: [...] }, e.g. "'@smoke', '@critical'"
33
38
  }
34
39
 
35
40
  export interface StepTemplateData {
@@ -57,7 +62,7 @@ export interface TestGeneratorAdapter {
57
62
  // Template rendering methods
58
63
  renderTestFile(data: TestFileData): string;
59
64
  renderScenario(data: ScenarioData): string;
60
- renderImports(options?: { runtimeData?: boolean; basePath?: string }): string;
65
+ renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean }): string;
61
66
  renderBeforeEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
62
67
  renderBeforeAll(data: { steps: Array<{ comment?: string; code: string }> }): string;
63
68
  renderAfterEach(data: { steps: Array<{ comment?: string; code: string }> }): string;
@@ -26,7 +26,7 @@ export class PlaywrightAdapter implements TestGeneratorAdapter {
26
26
  return this.templateEngine.renderScenario(data);
27
27
  }
28
28
 
29
- renderImports(options?: { runtimeData?: boolean; basePath?: string }): string {
29
+ renderImports(options?: { runtimeData?: boolean; basePath?: string; isParallel?: boolean; needsCleanupImport?: boolean }): string {
30
30
  return this.templateEngine.renderImports(options);
31
31
  }
32
32
 
@@ -1,4 +1,5 @@
1
- import { test, expect } from '{{basePath}}/base';
1
+ import { test, expect{{#if needsCleanupImport}}, cleanupPage{{/if}} } from '{{basePath}}/base';
2
+ import { type Page, type BrowserContext } from '@playwright/test';
2
3
  {{#if runtimeData}}
3
4
  import { TestDataLoader } from '{{basePath}}/test-data';
4
5
  {{/if}}
@@ -1,8 +1,27 @@
1
+ {{#if isParallel}}
2
+ {{#if tags}}
3
+ test('{{scenarioName}}', { tag: [{{{tags}}}] }, async ({ page }) => {
4
+ {{else}}
1
5
  test('{{scenarioName}}', async ({ page }) => {
6
+ {{/if}}
7
+ {{#each steps}}
8
+ {{#if comment}}
9
+ // {{comment}}
10
+ {{/if}}
11
+ {{code}}
12
+ {{/each}}
13
+ });
14
+ {{else}}
15
+ {{#if tags}}
16
+ test('{{scenarioName}}', { tag: [{{{tags}}}] }, async () => {
17
+ {{else}}
18
+ test('{{scenarioName}}', async () => {
19
+ {{/if}}
2
20
  {{#each steps}}
3
21
  {{#if comment}}
4
22
  // {{comment}}
5
23
  {{/if}}
6
24
  {{code}}
7
25
  {{/each}}
8
- });
26
+ });
27
+ {{/if}}
@@ -11,14 +11,11 @@ const testData = TestDataLoader.load('{{screenName}}', '{{featureFileName}}');
11
11
  */
12
12
  {{/if}}
13
13
 
14
+ {{#if isParallel}}
14
15
  test.describe('{{featureName}}', () => {
15
16
  {{#if singleAuthRole}}
16
17
  test.use({ storageState: 'specs/.auth/{{singleAuthRole}}.json' });
17
18
 
18
- {{/if}}
19
- {{#if cleanupConfig}}
20
- test.use({ autoCleanup: { {{cleanupConfig}} } });
21
-
22
19
  {{/if}}
23
20
  {{#if screenshotOnFailure}}
24
21
  test.use({ screenshotOnFailure: true });
@@ -66,3 +63,86 @@ test.describe('{{featureName}}', () => {
66
63
  {{/each}}
67
64
  {{/if}}
68
65
  });
66
+ {{else}}
67
+ test.describe.serial('{{featureName}}', () => {
68
+ let page: Page;
69
+ let context: BrowserContext;
70
+
71
+ {{#if singleAuthRole}}
72
+ test.beforeAll(async ({ browser }) => {
73
+ context = await browser.newContext({ storageState: 'specs/.auth/{{singleAuthRole}}.json' });
74
+ page = await context.newPage();
75
+ {{#each backgroundSteps}}
76
+ {{#if comment}}
77
+ // {{comment}}
78
+ {{/if}}
79
+ {{code}}
80
+ {{/each}}
81
+ });
82
+ {{else}}
83
+ test.beforeAll(async ({ browser }) => {
84
+ context = await browser.newContext();
85
+ page = await context.newPage();
86
+ {{#each backgroundSteps}}
87
+ {{#if comment}}
88
+ // {{comment}}
89
+ {{/if}}
90
+ {{code}}
91
+ {{/each}}
92
+ });
93
+ {{/if}}
94
+
95
+ test.afterAll(async () => {
96
+ await page.close();
97
+ await context.close();
98
+ });
99
+
100
+ {{#if cleanup}}
101
+ test.afterEach(async () => {
102
+ await cleanupPage(page, { {{cleanupConfig}} });
103
+ });
104
+
105
+ {{/if}}
106
+ {{#if screenshotOnFailure}}
107
+ test.use({ screenshotOnFailure: true });
108
+
109
+ {{/if}}
110
+ {{#if beforeAll}}
111
+ {{beforeAll}}
112
+
113
+ {{/if}}
114
+ {{#if afterEach}}
115
+ {{afterEach}}
116
+
117
+ {{/if}}
118
+ {{#if afterAll}}
119
+ {{afterAll}}
120
+
121
+ {{/if}}
122
+ {{#if authGroups}}
123
+ {{#each authGroups}}
124
+ {{#if authRole}}
125
+ test.describe('{{authRole}}', () => {
126
+ test.use({ storageState: 'specs/.auth/{{authRole}}.json' });
127
+
128
+ {{#each scenarios}}
129
+ {{indent this 2}}
130
+
131
+ {{/each}}
132
+ });
133
+
134
+ {{else}}
135
+ {{#each scenarios}}
136
+ {{this}}
137
+
138
+ {{/each}}
139
+ {{/if}}
140
+ {{/each}}
141
+ {{else}}
142
+ {{#each scenarios}}
143
+ {{this}}
144
+
145
+ {{/each}}
146
+ {{/if}}
147
+ });
148
+ {{/if}}