@activate-core/test-forge 0.6.0 → 0.7.0

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/index.js CHANGED
@@ -22,6 +22,8 @@ import { runInit } from '../core/init.js';
22
22
  import { runSetup } from '../core/setup.js';
23
23
  import { runUpdate } from '../core/update.js';
24
24
  import { runEnvTemplate } from '../core/env-template.js';
25
+ import { loadConfig } from '../core/config.js';
26
+ import { dirname } from 'node:path';
25
27
  const command = process.argv[2];
26
28
  const useBasic = process.argv.includes('--basic');
27
29
  const force = process.argv.includes('--force');
@@ -39,6 +41,22 @@ function flagValue(name) {
39
41
  function sourceOptions() {
40
42
  return { reference: flagValue('--reference'), source: flagValue('--source') };
41
43
  }
44
+ /**
45
+ * The output dirs to scan for env vars the GENERATED suite reads (test
46
+ * output dirs + their sibling helpers/ + the seed dir). Shared by the
47
+ * `generate` and `env` commands so the example always reflects the suite.
48
+ */
49
+ function generatedScanDirs() {
50
+ const cfg = loadConfig();
51
+ const dirs = new Set();
52
+ for (const out of Object.values(cfg.testOutput ?? {})) {
53
+ dirs.add(out);
54
+ dirs.add(dirname(out));
55
+ }
56
+ if (cfg.seedOutput)
57
+ dirs.add(cfg.seedOutput);
58
+ return [...dirs];
59
+ }
42
60
  function printUsage() {
43
61
  console.log(`testforge v${VERSION}`);
44
62
  console.log('');
@@ -87,7 +105,7 @@ async function main() {
87
105
  console.log('then run `testforge generate`.');
88
106
  }
89
107
  else if (command === 'env') {
90
- runEnvTemplate('docs', '.env.testing.example');
108
+ runEnvTemplate('docs', '.env.testing.example', '.', generatedScanDirs());
91
109
  }
92
110
  else if (command === 'generate') {
93
111
  const useAI = !useBasic;
@@ -130,6 +148,11 @@ async function main() {
130
148
  if (seedFailures > 0) {
131
149
  console.log(`Warning: ${seedFailures} seed file(s) could not be generated — see notes above.`);
132
150
  }
151
+ // Auto-refresh the env template so running `generate` on its own (not
152
+ // just `setup`) tells the developer exactly which credentials the
153
+ // generated suite needs — including vars the tests themselves read.
154
+ console.log('');
155
+ runEnvTemplate('docs', '.env.testing.example', '.', generatedScanDirs());
133
156
  }
134
157
  }
135
158
  else {
@@ -49,6 +49,11 @@ export function buildPrompt(journey, map, testPath, focus, harnessPath) {
49
49
  const framework = isApi
50
50
  ? 'Jest (with supertest for HTTP) for an API/backend test'
51
51
  : 'Playwright for a web/UI test';
52
+ // Pending-test syntax differs by runner: Jest has test.todo/it.skip;
53
+ // Playwright has NEITHER (test.todo throws) — it uses test.fixme/skip.
54
+ const pending = isApi
55
+ ? 'test.todo(...) or it.skip(...)'
56
+ : 'test.fixme(title, async () => {}) or test.skip(title, async () => {}) — Playwright has NO test.todo/it.*';
52
57
  const ext = config.testExtension ?? '.test.ts';
53
58
  const isJs = ext.endsWith('.js');
54
59
  const language = isJs
@@ -86,7 +91,7 @@ export function buildPrompt(journey, map, testPath, focus, harnessPath) {
86
91
  `assert that a vulnerability exists when the journey doc EXPLICITLY`,
87
92
  `documents it as a known/accepted behavior. If you cannot tell from the`,
88
93
  `journey or map whether a defense exists, write the check as`,
89
- `test.todo(...) or it.skip(...) with a "// TODO:" note — never assume a`,
94
+ `${pending} with a "// TODO:" note — never assume a`,
90
95
  `guard is missing and produce a failing test that falsely brands the`,
91
96
  `product as vulnerable.`,
92
97
  ``,
@@ -98,9 +103,9 @@ export function buildPrompt(journey, map, testPath, focus, harnessPath) {
98
103
  `models it touches. Confirm the REAL endpoint paths, HTTP methods,`,
99
104
  `request/response shapes, status codes, auth requirements, and function`,
100
105
  `signatures from the code. Base every assertion on what the code`,
101
- `actually does. Only fall back to "// TODO:" or test.todo/it.skip when`,
106
+ `actually does. Only fall back to a pending test (${pending}) when`,
102
107
  `the behavior genuinely cannot be determined from the source — do not`,
103
- `leave something as a TODO that a quick read of the code would answer.`,
108
+ `leave something pending that a quick read of the code would answer.`,
104
109
  `Do NOT write or edit any file; output only the test code to stdout.`,
105
110
  ``,
106
111
  isApi
@@ -119,6 +124,11 @@ export function buildPrompt(journey, map, testPath, focus, harnessPath) {
119
124
  `the UI. Use placeholder selectors with a TODO comment where the`,
120
125
  `real page structure is unknown — do not invent selectors as if`,
121
126
  `they are real.`,
127
+ `CRITICAL Playwright API: import { test, expect } from`,
128
+ `'@playwright/test'. There is NO test.todo / it.* / describe.skip`,
129
+ `in Playwright — using them throws at collection and breaks the`,
130
+ `WHOLE file. For a pending case use test.fixme(title, async () =>`,
131
+ `{}) or test.skip(...); group with test.describe(...).`,
122
132
  ].join('\n'),
123
133
  ``,
124
134
  ...(testPath
@@ -55,7 +55,8 @@ const SOURCE_EXT = /\.(c|m)?[jt]sx?$/i;
55
55
  * skipping vendored/build/test output, with a file cap so a huge repo
56
56
  * can't stall the run.
57
57
  */
58
- function scanSourceEnvVars(srcDir, maxFiles = 4000) {
58
+ function scanSourceEnvVars(srcDir, opts = {}) {
59
+ const { includeTests = false, maxFiles = 4000 } = opts;
59
60
  const vars = new Set();
60
61
  let seen = 0;
61
62
  const walk = (dir) => {
@@ -78,8 +79,10 @@ function scanSourceEnvVars(srcDir, maxFiles = 4000) {
78
79
  walk(full);
79
80
  }
80
81
  else if (SOURCE_EXT.test(entry.name)) {
81
- // Skip test files — we want app source, not existing tests.
82
- if (/\.(test|spec)\./i.test(entry.name))
82
+ // App-source scan skips test files; the generated-suite scan
83
+ // (includeTests) WANTS them — that is where the tests' own env
84
+ // vars (tokens, ids, base URLs) live.
85
+ if (!includeTests && /\.(test|spec)\./i.test(entry.name))
83
86
  continue;
84
87
  seen++;
85
88
  try {
@@ -108,7 +111,7 @@ function areaAndJourney(file) {
108
111
  // Fallback: flat file with no area folder.
109
112
  return { area: 'general', journey: noExt };
110
113
  }
111
- export function runEnvTemplate(docsDir, outPath, srcDir = '.') {
114
+ export function runEnvTemplate(docsDir, outPath, srcDir = '.', testDirs = []) {
112
115
  if (!existsSync(docsDir)) {
113
116
  console.log(`No docs found at "${docsDir}". Run \`testforge setup\` first.`);
114
117
  return;
@@ -153,7 +156,21 @@ export function runEnvTemplate(docsDir, outPath, srcDir = '.') {
153
156
  }
154
157
  const sourceVars = scanSourceEnvVars(srcDir);
155
158
  const bootVars = [...sourceVars].filter((v) => !knownFromDocs.has(v)).sort();
156
- if (byArea.size === 0 && bootVars.length === 0) {
159
+ // Vars the GENERATED suite (tests + seeds + testkit) actually reads —
160
+ // scanned from the output dirs, INCLUDING test files. This is what
161
+ // captures tokens/ids/base-URLs the generated code needs but that no
162
+ // doc/source mentioned.
163
+ const suiteScan = new Set();
164
+ for (const d of testDirs) {
165
+ for (const v of scanSourceEnvVars(d, { includeTests: true })) {
166
+ suiteScan.add(v);
167
+ }
168
+ }
169
+ const alreadyShown = new Set([...knownFromDocs, ...bootVars]);
170
+ const suiteVars = [...suiteScan]
171
+ .filter((v) => !alreadyShown.has(v))
172
+ .sort();
173
+ if (byArea.size === 0 && bootVars.length === 0 && suiteVars.length === 0) {
157
174
  console.log('No environment variables found in the docs or source.');
158
175
  return;
159
176
  }
@@ -167,6 +184,15 @@ export function runEnvTemplate(docsDir, outPath, srcDir = '.') {
167
184
  '# Keep .env.testing OUT of version control.',
168
185
  '',
169
186
  ];
187
+ if (suiteVars.length > 0) {
188
+ lines.push(`# ${'='.repeat(40)}`);
189
+ lines.push('# GENERATED SUITE (env the generated tests/testkit read)');
190
+ lines.push(`# ${'='.repeat(40)}`);
191
+ for (const v of suiteVars) {
192
+ lines.push(`${v}= # used by: generated tests`);
193
+ }
194
+ lines.push('');
195
+ }
170
196
  if (bootVars.length > 0) {
171
197
  lines.push(`# ${'='.repeat(40)}`);
172
198
  lines.push('# APP (read directly from source — REQUIRED to boot)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@activate-core/test-forge",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI-powered QA automation. Scans a project, drafts journey docs, and generates seeded Jest/Playwright tests. Works in any project, with any AI CLI.",
5
5
  "type": "module",
6
6
  "bin": {