browsecraft 0.2.0 → 0.3.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.cjs +19 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +19 -3
- package/dist/cli.js.map +1 -1
- package/package.json +4 -4
package/dist/cli.cjs
CHANGED
|
@@ -7,7 +7,7 @@ var path = require('path');
|
|
|
7
7
|
var url = require('url');
|
|
8
8
|
var browsecraftRunner = require('browsecraft-runner');
|
|
9
9
|
|
|
10
|
-
var VERSION = "0.
|
|
10
|
+
var VERSION = "0.2.0";
|
|
11
11
|
async function main() {
|
|
12
12
|
const args = process.argv.slice(2);
|
|
13
13
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
@@ -193,7 +193,11 @@ async function loadConfig() {
|
|
|
193
193
|
const configPath = path.resolve(cwd, name);
|
|
194
194
|
if (fs.existsSync(configPath)) {
|
|
195
195
|
try {
|
|
196
|
-
|
|
196
|
+
if (name.endsWith(".ts") || name.endsWith(".mts")) {
|
|
197
|
+
await ensureTypeScriptLoader();
|
|
198
|
+
}
|
|
199
|
+
const fileUrl = url.pathToFileURL(configPath).href;
|
|
200
|
+
const mod = await import(fileUrl);
|
|
197
201
|
return mod.default ?? mod;
|
|
198
202
|
} catch {
|
|
199
203
|
console.warn(`Warning: Could not load ${name}. Using defaults.`);
|
|
@@ -210,11 +214,23 @@ async function ensureTypeScriptLoader() {
|
|
|
210
214
|
tsLoaderRegistered = true;
|
|
211
215
|
return;
|
|
212
216
|
}
|
|
217
|
+
const { createRequire } = await import('module');
|
|
218
|
+
const userRequire = createRequire(url.pathToFileURL(path.resolve(process.cwd(), "package.json")));
|
|
219
|
+
try {
|
|
220
|
+
const tsxApiPath = userRequire.resolve("tsx/esm/api");
|
|
221
|
+
const { register } = await import(url.pathToFileURL(tsxApiPath).href);
|
|
222
|
+
if (typeof register === "function") {
|
|
223
|
+
register();
|
|
224
|
+
tsLoaderRegistered = true;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
213
229
|
for (const loader of ["tsx/esm", "ts-node/esm"]) {
|
|
214
230
|
try {
|
|
215
231
|
const { register } = await import('module');
|
|
216
232
|
if (typeof register === "function") {
|
|
217
|
-
register(loader, url.pathToFileURL("
|
|
233
|
+
register(loader, url.pathToFileURL(path.resolve(process.cwd(), "/")));
|
|
218
234
|
tsLoaderRegistered = true;
|
|
219
235
|
return;
|
|
220
236
|
}
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"names":["resolveConfig","TestRunner","testRegistry","pathToFileURL","Browser","runTest","join","existsSync","writeFileSync","mkdirSync","resolve"],"mappings":";;;;;;;;;AAmBA,IAAM,OAAA,GAAU,OAAA;AAEhB,eAAe,IAAA,GAAO;AACrB,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AAEjC,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG;AACxE,IAAA,SAAA,EAAU;AACV,IAAA;AAAA,EACD;AAEA,EAAA,IAAI,KAAK,QAAA,CAAS,WAAW,KAAK,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG;AACtD,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,aAAA,EAAgB,OAAO,CAAA,CAAE,CAAA;AACrC,IAAA;AAAA,EACD;AAEA,EAAA,MAAM,OAAA,GAAU,KAAK,CAAC,CAAA;AAEtB,EAAA,QAAQ,OAAA;AAAS,IAChB,KAAK,MAAA;AACJ,MAAA,MAAM,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AAC5B,MAAA;AAAA,IACD,KAAK,MAAA;AACJ,MAAA,MAAM,WAAA,EAAY;AAClB,MAAA;AAAA,IACD;AAEC,MAAA,IAAI,OAAA,KAAY,QAAQ,QAAA,CAAS,KAAK,KAAK,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA,CAAA,EAAI;AACpE,QAAA,MAAM,SAAS,IAAI,CAAA;AAAA,MACpB,CAAA,MAAO;AACN,QAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,iBAAA,EAAoB,OAAO,CAAA,CAAE,CAAA;AAC3C,QAAA,OAAA,CAAQ,MAAM,iDAAiD,CAAA;AAC/D,QAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,MACf;AAAA;AAEH;AAMA,eAAe,SAAS,IAAA,EAAgB;AAEvC,EAAA,MAAM,KAAA,GAAQ,WAAW,IAAI,CAAA;AAC7B,EAAA,MAAM,YAAA,GAAe,KAAK,MAAA,CAAO,CAAC,MAAM,CAAC,CAAA,CAAE,UAAA,CAAW,IAAI,CAAC,CAAA;AAG3D,EAAA,MAAM,UAAA,GAAa,MAAM,UAAA,EAAW;AACpC,EAAA,MAAM,MAAA,GAASA,gCAAc,UAAU,CAAA;AAGvC,EAAA,IAAI,KAAA,CAAM,MAAA,IAAU,KAAA,CAAM,QAAA,KAAa,KAAA,EAAO;AAC7C,IAAA,MAAA,CAAO,QAAA,GAAW,KAAA;AAAA,EACnB;AACA,EAAA,IAAI,MAAM,OAAA,EAAS;AAClB,IAAA,MAAA,CAAO,UAAU,KAAA,CAAM,OAAA;AAAA,EACxB;AACA,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAChC,IAAA,MAAA,CAAO,UAAU,KAAA,CAAM,OAAA;AAAA,EACxB;AACA,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAChC,IAAA,MAAA,CAAO,UAAU,KAAA,CAAM,OAAA;AAAA,EACxB;AACA,EAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAW;AAChC,IAAA,MAAA,CAAO,UAAU,KAAA,CAAM,OAAA;AAAA,EACxB;AACA,EAAA,IAAI,MAAM,KAAA,EAAO;AAChB,IAAA,MAAA,CAAO,KAAA,GAAQ,IAAA;AAAA,EAChB;AAGA,EAAA,MAAM,aAAA,GAA+B;AAAA,IACpC,MAAA;AAAA,IACA,KAAA,EAAO,YAAA,CAAa,MAAA,GAAS,CAAA,GAAI,YAAA,GAAe,MAAA;AAAA,IAChD,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,MAAM,KAAA,CAAM;AAAA,GACb;AAGA,EAAA,MAAM,MAAA,GAAS,IAAIC,4BAAA,CAAW,aAAa,CAAA;AAG3C,EAAA,MAAM,QAAA,GAAW,OAAO,IAAA,KAA0C;AACjE,IAAA,MAAM,WAAWC,8BAAA,CAAa,MAAA;AAG9B,IAAA,IAAI,KAAK,QAAA,CAAS,KAAK,KAAK,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG;AAClD,MAAA,MAAM,sBAAA,EAAuB;AAAA,IAC9B;AAEA,IAAA,MAAM,OAAA,GAAUC,iBAAA,CAAc,IAAI,CAAA,CAAE,IAAA;AACpC,IAAA,MAAM,OAAO,OAAA,CAAA;AACb,IAAA,OAAOD,+BAAa,KAAA,CAAM,QAAQ,CAAA,CAAE,GAAA,CAAI,CAAC,EAAA,MAAQ;AAAA,MAChD,OAAO,EAAA,CAAG,KAAA;AAAA,MACV,WAAW,EAAA,CAAG,SAAA;AAAA,MACd,MAAM,EAAA,CAAG,IAAA;AAAA,MACT,MAAM,EAAA,CAAG,IAAA;AAAA,MACT,SAAS,EAAA,CAAG,OAAA;AAAA,MACZ,IAAI,EAAA,CAAG;AAAA,KACR,CAAE,CAAA;AAAA,EACH,CAAA;AAGA,EAAA,IAAI,aAAA;AAEJ,EAAA,IAAI;AACH,IAAA,aAAA,GAAgB,MAAME,0BAAQ,MAAA,CAAO;AAAA,MACpC,SAAS,MAAA,CAAO,OAAA;AAAA,MAChB,UAAU,MAAA,CAAO,QAAA;AAAA,MACjB,gBAAgB,MAAA,CAAO,cAAA;AAAA,MACvB,OAAO,MAAA,CAAO,KAAA;AAAA,MACd,SAAS,MAAA,CAAO;AAAA,KAChB,CAAA;AAAA,EACF,SAAS,GAAA,EAAK;AACb,IAAA,OAAA,CAAQ,KAAA,CAAM,6BAA6B,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAE,CAAA;AAC7F,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EACf;AAGA,EAAA,MAAM,WAAA,GAAc,OAAO,IAAA,KAAuB;AACjD,IAAA,OAAOC,yBAAA,CAAQ,MAA6B,aAAa,CAAA;AAAA,EAC1D,CAAA;AAEA,EAAA,IAAI;AACH,IAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,GAAA,CAAI,UAAU,WAAW,CAAA;AACvD,IAAA,MAAM,aAAA,CAAc,KAAA,EAAM,CAAE,KAAA,CAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAC1C,IAAA,OAAA,CAAQ,KAAK,QAAQ,CAAA;AAAA,EACtB,SAAS,GAAA,EAAK;AACb,IAAA,MAAM,aAAA,EAAe,KAAA,EAAM,CAAE,KAAA,CAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAC3C,IAAA,MAAM,GAAA;AAAA,EACP;AACD;AAMA,eAAe,WAAA,GAAc;AAC5B,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,EAAI;AAExB,EAAA,OAAA,CAAQ,IAAI,mCAAmC,CAAA;AAG/C,EAAA,MAAM,UAAA,GAAaC,SAAA,CAAK,GAAA,EAAK,uBAAuB,CAAA;AACpD,EAAA,IAAI,CAACC,aAAA,CAAW,UAAU,CAAA,EAAG;AAC5B,IAAAC,gBAAA;AAAA,MACC,UAAA;AAAA,MACA,CAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,KAmBD;AACA,IAAA,OAAA,CAAQ,IAAI,iCAAiC,CAAA;AAAA,EAC9C,CAAA,MAAO;AACN,IAAA,OAAA,CAAQ,IAAI,kDAAkD,CAAA;AAAA,EAC/D;AAGA,EAAA,MAAM,QAAA,GAAWF,SAAA,CAAK,GAAA,EAAK,OAAO,CAAA;AAClC,EAAA,IAAI,CAACC,aAAA,CAAW,QAAQ,CAAA,EAAG;AAC1B,IAAAE,YAAA,CAAU,QAAA,EAAU,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,WAAA,GAAcH,SAAA,CAAK,QAAA,EAAU,iBAAiB,CAAA;AACpD,EAAA,IAAI,CAACC,aAAA,CAAW,WAAW,CAAA,EAAG;AAC7B,IAAAC,gBAAA;AAAA,MACC,WAAA;AAAA,MACA,CAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAaD;AACA,IAAA,OAAA,CAAQ,IAAI,iCAAiC,CAAA;AAAA,EAC9C,CAAA,MAAO;AACN,IAAA,OAAA,CAAQ,IAAI,kDAAkD,CAAA;AAAA,EAC/D;AAGA,EAAA,MAAM,aAAA,GAAgBF,SAAA,CAAK,GAAA,EAAK,YAAY,CAAA;AAC5C,EAAA,IAAIC,aAAA,CAAW,aAAa,CAAA,EAAG;AAC9B,IAAA,MAAM,OAAA,GAAU,MAAM,OAAO,IAAS,CAAA,CAAE,IAAA,CAAK,CAAC,EAAA,KAAO,EAAA,CAAG,YAAA,CAAa,aAAA,EAAe,OAAO,CAAC,CAAA;AAC5F,IAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,cAAc,CAAA,EAAG;AACtC,MAAAC,gBAAA,CAAc,aAAA,EAAe,CAAA,EAAG,OAAA,CAAQ,OAAA,EAAS;;AAAA;AAAA;AAAA,CAAoC,CAAA;AACrF,MAAA,OAAA,CAAQ,IAAI,sBAAsB,CAAA;AAAA,IACnC;AAAA,EACD;AAEA,EAAA,OAAA,CAAQ,IAAI,4CAA4C,CAAA;AACxD,EAAA,OAAA,CAAQ,IAAI,4BAA4B,CAAA;AACzC;AAMA,eAAe,UAAA,GAA8C;AAC5D,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,EAAI;AACxB,EAAA,MAAM,UAAA,GAAa;AAAA,IAClB,uBAAA;AAAA,IACA,uBAAA;AAAA,IACA,wBAAA;AAAA,IACA;AAAA,GACD;AAEA,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC9B,IAAA,MAAM,UAAA,GAAaE,YAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AACpC,IAAA,IAAIH,aAAA,CAAW,UAAU,CAAA,EAAG;AAC3B,MAAA,IAAI;AAEH,QAAA,MAAM,GAAA,GAAM,MAAM,OAAO,UAAA,CAAA;AACzB,QAAA,OAAO,IAAI,OAAA,IAAW,GAAA;AAAA,MACvB,CAAA,CAAA,MAAQ;AAEP,QAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,wBAAA,EAA2B,IAAI,CAAA,iBAAA,CAAmB,CAAA;AAAA,MAChE;AAAA,IACD;AAAA,EACD;AAEA,EAAA,OAAO,MAAA;AACR;AAMA,IAAI,kBAAA,GAAqB,KAAA;AAMzB,eAAe,sBAAA,GAAwC;AACtD,EAAA,IAAI,kBAAA,EAAoB;AAIxB,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,QAAA,CAAS,IAAA,CAAK,GAAG,CAAA;AAC1C,EAAA,IACC,QAAA,CAAS,QAAA,CAAS,KAAK,CAAA,IACvB,SAAS,QAAA,CAAS,SAAS,CAAA,IAC3B,QAAA,CAAS,QAAA,CAAS,QAAQ,CAAA,IAC1B,OAAA,CAAQ,SAAS,GAAA,EAChB;AACD,IAAA,kBAAA,GAAqB,IAAA;AACrB,IAAA;AAAA,EACD;AAGA,EAAA,KAAA,MAAW,MAAA,IAAU,CAAC,SAAA,EAAW,aAAa,CAAA,EAAG;AAChD,IAAA,IAAI;AAEH,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,QAAa,CAAA;AAC/C,MAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AACnC,QAAA,QAAA,CAAS,MAAA,EAAQJ,iBAAA,CAAc,IAAI,CAAC,CAAA;AACpC,QAAA,kBAAA,GAAqB,IAAA;AACrB,QAAA;AAAA,MACD;AAAA,IACD,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACD;AAGA,EAAA,OAAA,CAAQ,KAAA;AAAA,IACP;AAAA,GAKD;AACA,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AACf;AAkBA,SAAS,WAAW,IAAA,EAA0B;AAC7C,EAAA,MAAM,QAAkB,EAAC;AAEzB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,GAAA,GAAM,KAAK,CAAC,CAAA;AAClB,IAAA,QAAQ,GAAA;AAAK,MACZ,KAAK,UAAA;AACJ,QAAA,KAAA,CAAM,MAAA,GAAS,IAAA;AACf,QAAA;AAAA,MACD,KAAK,YAAA;AACJ,QAAA,KAAA,CAAM,QAAA,GAAW,IAAA;AACjB,QAAA;AAAA,MACD,KAAK,SAAA;AACJ,QAAA,KAAA,CAAM,KAAA,GAAQ,IAAA;AACd,QAAA;AAAA,MACD,KAAK,QAAA;AACJ,QAAA,KAAA,CAAM,IAAA,GAAO,IAAA;AACb,QAAA;AAAA,MACD,KAAK,WAAA;AACJ,QAAA,KAAA,CAAM,OAAA,GAAU,IAAA,CAAK,EAAE,CAAC,CAAA;AACxB,QAAA;AAAA,MACD,KAAK,WAAA;AACJ,QAAA,KAAA,CAAM,OAAA,GAAU,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,CAAA,IAAK,KAAK,EAAE,CAAA;AACpD,QAAA;AAAA,MACD,KAAK,WAAA;AACJ,QAAA,KAAA,CAAM,OAAA,GAAU,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,CAAA,IAAK,SAAS,EAAE,CAAA;AACxD,QAAA;AAAA,MACD,KAAK,WAAA;AACJ,QAAA,KAAA,CAAM,OAAA,GAAU,OAAO,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,CAAA,IAAK,KAAK,EAAE,CAAA;AACpD,QAAA;AAAA,MACD,KAAK,QAAA;AAAA,MACL,KAAK,IAAA;AACJ,QAAA,KAAA,CAAM,IAAA,GAAO,IAAA,CAAK,EAAE,CAAC,CAAA;AACrB,QAAA;AAAA;AACF,EACD;AAEA,EAAA,OAAO,KAAA;AACR;AAMA,SAAS,SAAA,GAAY;AACpB,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,eAAA,EACI,OAAO,CAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CA4BvB,CAAA;AACD;AAMA,IAAA,EAAK,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AACrB,EAAA,OAAA,CAAQ,KAAA,CAAM,cAAA,EAAgB,GAAA,CAAI,OAAO,CAAA;AACzC,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AACf,CAAC,CAAA","file":"cli.cjs","sourcesContent":["#!/usr/bin/env node\n// ============================================================================\n// Browsecraft - CLI\n// The command-line interface for running tests and initializing projects.\n//\n// npx browsecraft test # Run all tests\n// npx browsecraft test login.test.ts # Run specific file\n// npx browsecraft init # Scaffold a new project\n// npx browsecraft --help # Show help\n// ============================================================================\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { type RunnableTest, type RunnerOptions, TestRunner } from 'browsecraft-runner';\nimport { Browser } from './browser.js';\nimport { type UserConfig, resolveConfig } from './config.js';\nimport { type TestCase, runAfterAllHooks, runTest, testRegistry } from './test.js';\n\nconst VERSION = '0.1.0';\n\nasync function main() {\n\tconst args = process.argv.slice(2);\n\n\tif (args.length === 0 || args.includes('--help') || args.includes('-h')) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\tif (args.includes('--version') || args.includes('-v')) {\n\t\tconsole.log(`browsecraft v${VERSION}`);\n\t\treturn;\n\t}\n\n\tconst command = args[0];\n\n\tswitch (command) {\n\t\tcase 'test':\n\t\t\tawait runTests(args.slice(1));\n\t\t\tbreak;\n\t\tcase 'init':\n\t\t\tawait initProject();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t// If no command, assume it's a file path to test\n\t\t\tif (command && (command.endsWith('.ts') || command.endsWith('.js'))) {\n\t\t\t\tawait runTests(args);\n\t\t\t} else {\n\t\t\t\tconsole.error(`Unknown command: ${command}`);\n\t\t\t\tconsole.error('Run \"browsecraft --help\" for usage information.');\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Test Command\n// ---------------------------------------------------------------------------\n\nasync function runTests(args: string[]) {\n\t// Parse CLI flags\n\tconst flags = parseFlags(args);\n\tconst filePatterns = args.filter((a) => !a.startsWith('--'));\n\n\t// Load config file if it exists\n\tconst userConfig = await loadConfig();\n\tconst config = resolveConfig(userConfig);\n\n\t// Apply CLI overrides\n\tif (flags.headed || flags.headless === false) {\n\t\tconfig.headless = false;\n\t}\n\tif (flags.browser) {\n\t\tconfig.browser = flags.browser as any;\n\t}\n\tif (flags.workers !== undefined) {\n\t\tconfig.workers = flags.workers;\n\t}\n\tif (flags.timeout !== undefined) {\n\t\tconfig.timeout = flags.timeout;\n\t}\n\tif (flags.retries !== undefined) {\n\t\tconfig.retries = flags.retries;\n\t}\n\tif (flags.debug) {\n\t\tconfig.debug = true;\n\t}\n\n\t// Set up runner options\n\tconst runnerOptions: RunnerOptions = {\n\t\tconfig,\n\t\tfiles: filePatterns.length > 0 ? filePatterns : undefined,\n\t\tgrep: flags.grep,\n\t\tbail: flags.bail,\n\t};\n\n\t// Run tests\n\tconst runner = new TestRunner(runnerOptions);\n\n\t// loadFile callback: imports the test file and returns registered tests\n\tconst loadFile = async (file: string): Promise<RunnableTest[]> => {\n\t\tconst startIdx = testRegistry.length;\n\n\t\t// For TypeScript files, register a TypeScript loader if available\n\t\tif (file.endsWith('.ts') || file.endsWith('.mts')) {\n\t\t\tawait ensureTypeScriptLoader();\n\t\t}\n\n\t\tconst fileUrl = pathToFileURL(file).href;\n\t\tawait import(fileUrl);\n\t\treturn testRegistry.slice(startIdx).map((tc) => ({\n\t\t\ttitle: tc.title,\n\t\t\tsuitePath: tc.suitePath,\n\t\t\tskip: tc.skip,\n\t\t\tonly: tc.only,\n\t\t\toptions: tc.options,\n\t\t\tfn: tc.fn as (fixtures: unknown) => Promise<void>,\n\t\t}));\n\t};\n\n\t// Launch a shared browser for all tests\n\tlet sharedBrowser: Browser | undefined;\n\n\ttry {\n\t\tsharedBrowser = await Browser.launch({\n\t\t\tbrowser: config.browser,\n\t\t\theadless: config.headless,\n\t\t\texecutablePath: config.executablePath,\n\t\t\tdebug: config.debug,\n\t\t\ttimeout: config.timeout,\n\t\t});\n\t} catch (err) {\n\t\tconsole.error(`Failed to launch browser: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t}\n\n\t// executeTest callback: runs a single test with fixture setup/teardown\n\tconst executeTest = async (test: RunnableTest) => {\n\t\treturn runTest(test as unknown as TestCase, sharedBrowser);\n\t};\n\n\ttry {\n\t\tconst exitCode = await runner.run(loadFile, executeTest);\n\t\tawait sharedBrowser.close().catch(() => {});\n\t\tprocess.exit(exitCode);\n\t} catch (err) {\n\t\tawait sharedBrowser?.close().catch(() => {});\n\t\tthrow err;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Init Command\n// ---------------------------------------------------------------------------\n\nasync function initProject() {\n\tconst cwd = process.cwd();\n\n\tconsole.log('\\n Browsecraft - Project Setup\\n');\n\n\t// Create config file\n\tconst configPath = join(cwd, 'browsecraft.config.ts');\n\tif (!existsSync(configPath)) {\n\t\twriteFileSync(\n\t\t\tconfigPath,\n\t\t\t`import { defineConfig } from 'browsecraft';\n\nexport default defineConfig({\n // Browser to use: 'chrome' | 'firefox' | 'edge'\n browser: 'chrome',\n\n // Run tests in headless mode\n headless: true,\n\n // Base URL for page.goto() calls\n // baseURL: 'http://localhost:3000',\n\n // Global timeout for actions (ms)\n timeout: 30_000,\n\n // Take screenshots on failure\n screenshot: 'on-failure',\n});\n`,\n\t\t);\n\t\tconsole.log(' Created browsecraft.config.ts');\n\t} else {\n\t\tconsole.log(' browsecraft.config.ts already exists, skipping');\n\t}\n\n\t// Create example test\n\tconst testsDir = join(cwd, 'tests');\n\tif (!existsSync(testsDir)) {\n\t\tmkdirSync(testsDir, { recursive: true });\n\t}\n\n\tconst exampleTest = join(testsDir, 'example.test.ts');\n\tif (!existsSync(exampleTest)) {\n\t\twriteFileSync(\n\t\t\texampleTest,\n\t\t\t`import { test, expect } from 'browsecraft';\n\ntest('homepage has correct title', async ({ page }) => {\n await page.goto('https://example.com');\n await expect(page).toHaveTitle('Example Domain');\n});\n\ntest('can navigate to more info', async ({ page }) => {\n await page.goto('https://example.com');\n await page.click('More information');\n await expect(page).toHaveURL(/iana\\\\.org/);\n});\n`,\n\t\t);\n\t\tconsole.log(' Created tests/example.test.ts');\n\t} else {\n\t\tconsole.log(' tests/example.test.ts already exists, skipping');\n\t}\n\n\t// Add .browsecraft to .gitignore\n\tconst gitignorePath = join(cwd, '.gitignore');\n\tif (existsSync(gitignorePath)) {\n\t\tconst content = await import('node:fs').then((fs) => fs.readFileSync(gitignorePath, 'utf-8'));\n\t\tif (!content.includes('.browsecraft')) {\n\t\t\twriteFileSync(gitignorePath, `${content.trimEnd()}\\n\\n# Browsecraft\\n.browsecraft/\\n`);\n\t\t\tconsole.log(' Updated .gitignore');\n\t\t}\n\t}\n\n\tconsole.log('\\n Setup complete! Run your first test:\\n');\n\tconsole.log(' npx browsecraft test\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Config Loading\n// ---------------------------------------------------------------------------\n\nasync function loadConfig(): Promise<UserConfig | undefined> {\n\tconst cwd = process.cwd();\n\tconst candidates = [\n\t\t'browsecraft.config.ts',\n\t\t'browsecraft.config.js',\n\t\t'browsecraft.config.mjs',\n\t\t'browsecraft.config.mts',\n\t];\n\n\tfor (const name of candidates) {\n\t\tconst configPath = resolve(cwd, name);\n\t\tif (existsSync(configPath)) {\n\t\t\ttry {\n\t\t\t\t// For .ts files, we need tsx or ts-node to be available\n\t\t\t\tconst mod = await import(configPath);\n\t\t\t\treturn mod.default ?? mod;\n\t\t\t} catch {\n\t\t\t\t// Config file exists but couldn't be loaded -- continue with defaults\n\t\t\t\tconsole.warn(`Warning: Could not load ${name}. Using defaults.`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n// ---------------------------------------------------------------------------\n// TypeScript Loader\n// ---------------------------------------------------------------------------\n\nlet tsLoaderRegistered = false;\n\n/**\n * Ensure a TypeScript loader is registered so that .ts test files can be imported.\n * Tries tsx first (fastest), then ts-node, then falls back to a helpful error.\n */\nasync function ensureTypeScriptLoader(): Promise<void> {\n\tif (tsLoaderRegistered) return;\n\n\t// Check if we're already running under a TS loader (e.g., `tsx`, `ts-node`, `bun`)\n\t// In that case, .ts imports already work\n\tconst execArgs = process.execArgv.join(' ');\n\tif (\n\t\texecArgs.includes('tsx') ||\n\t\texecArgs.includes('ts-node') ||\n\t\texecArgs.includes('loader') ||\n\t\tprocess.versions.bun // Bun handles TS natively\n\t) {\n\t\ttsLoaderRegistered = true;\n\t\treturn;\n\t}\n\n\t// Try to dynamically register tsx or ts-node\n\tfor (const loader of ['tsx/esm', 'ts-node/esm']) {\n\t\ttry {\n\t\t\t// Node 20.6+ supports module.register()\n\t\t\tconst { register } = await import('node:module');\n\t\t\tif (typeof register === 'function') {\n\t\t\t\tregister(loader, pathToFileURL('./'));\n\t\t\t\ttsLoaderRegistered = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Loader not available, try next\n\t\t}\n\t}\n\n\t// If neither tsx nor ts-node is available, give a helpful error\n\tconsole.error(\n\t\t'\\n Error: Cannot import TypeScript test files.\\n' +\n\t\t\t' Install tsx (recommended) or ts-node:\\n\\n' +\n\t\t\t' npm install -D tsx\\n\\n' +\n\t\t\t' Or run browsecraft with tsx:\\n\\n' +\n\t\t\t' npx tsx node_modules/.bin/browsecraft test\\n',\n\t);\n\tprocess.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Flag parsing\n// ---------------------------------------------------------------------------\n\ninterface CLIFlags {\n\theaded?: boolean;\n\theadless?: boolean;\n\tbrowser?: string;\n\tworkers?: number;\n\ttimeout?: number;\n\tretries?: number;\n\tgrep?: string;\n\tbail?: boolean;\n\tdebug?: boolean;\n}\n\nfunction parseFlags(args: string[]): CLIFlags {\n\tconst flags: CLIFlags = {};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i]!;\n\t\tswitch (arg) {\n\t\t\tcase '--headed':\n\t\t\t\tflags.headed = true;\n\t\t\t\tbreak;\n\t\t\tcase '--headless':\n\t\t\t\tflags.headless = true;\n\t\t\t\tbreak;\n\t\t\tcase '--debug':\n\t\t\t\tflags.debug = true;\n\t\t\t\tbreak;\n\t\t\tcase '--bail':\n\t\t\t\tflags.bail = true;\n\t\t\t\tbreak;\n\t\t\tcase '--browser':\n\t\t\t\tflags.browser = args[++i];\n\t\t\t\tbreak;\n\t\t\tcase '--workers':\n\t\t\t\tflags.workers = Number.parseInt(args[++i] ?? '1', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--timeout':\n\t\t\t\tflags.timeout = Number.parseInt(args[++i] ?? '30000', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--retries':\n\t\t\t\tflags.retries = Number.parseInt(args[++i] ?? '0', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--grep':\n\t\t\tcase '-g':\n\t\t\t\tflags.grep = args[++i];\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn flags;\n}\n\n// ---------------------------------------------------------------------------\n// Help\n// ---------------------------------------------------------------------------\n\nfunction printHelp() {\n\tconsole.log(`\n browsecraft v${VERSION} -- AI-native browser testing\n\n Usage:\n browsecraft test [files...] [options]\n browsecraft init\n\n Commands:\n test Run browser tests\n init Create a new project with example config and test\n\n Options:\n --browser <name> Browser to use: chrome, firefox, edge (default: chrome)\n --headed Run in headed mode (show the browser)\n --headless Run in headless mode (default)\n --workers <n> Number of parallel workers (default: half CPU cores)\n --timeout <ms> Global timeout in milliseconds (default: 30000)\n --retries <n> Retry failed tests n times (default: 0)\n --grep <pattern> Only run tests matching pattern\n --bail Stop after first failure\n --debug Enable verbose debug logging\n -h, --help Show this help message\n -v, --version Show version\n\n Examples:\n browsecraft test # Run all tests\n browsecraft test tests/login.test.ts # Run specific file\n browsecraft test --headed --browser firefox\n browsecraft test --grep \"login\" --bail\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\nmain().catch((err) => {\n\tconsole.error('Fatal error:', err.message);\n\tprocess.exit(1);\n});\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":["resolveConfig","TestRunner","testRegistry","pathToFileURL","Browser","runTest","join","existsSync","writeFileSync","mkdirSync","resolve"],"mappings":"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","file":"cli.cjs","sourcesContent":["#!/usr/bin/env node\n// ============================================================================\n// Browsecraft - CLI\n// The command-line interface for running tests and initializing projects.\n//\n// npx browsecraft test # Run all tests\n// npx browsecraft test login.test.ts # Run specific file\n// npx browsecraft init # Scaffold a new project\n// npx browsecraft --help # Show help\n// ============================================================================\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { type RunnableTest, type RunnerOptions, TestRunner } from 'browsecraft-runner';\nimport { Browser } from './browser.js';\nimport { type UserConfig, resolveConfig } from './config.js';\nimport { type TestCase, runAfterAllHooks, runTest, testRegistry } from './test.js';\n\nconst VERSION = '0.2.0';\n\nasync function main() {\n\tconst args = process.argv.slice(2);\n\n\tif (args.length === 0 || args.includes('--help') || args.includes('-h')) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\tif (args.includes('--version') || args.includes('-v')) {\n\t\tconsole.log(`browsecraft v${VERSION}`);\n\t\treturn;\n\t}\n\n\tconst command = args[0];\n\n\tswitch (command) {\n\t\tcase 'test':\n\t\t\tawait runTests(args.slice(1));\n\t\t\tbreak;\n\t\tcase 'init':\n\t\t\tawait initProject();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t// If no command, assume it's a file path to test\n\t\t\tif (command && (command.endsWith('.ts') || command.endsWith('.js'))) {\n\t\t\t\tawait runTests(args);\n\t\t\t} else {\n\t\t\t\tconsole.error(`Unknown command: ${command}`);\n\t\t\t\tconsole.error('Run \"browsecraft --help\" for usage information.');\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Test Command\n// ---------------------------------------------------------------------------\n\nasync function runTests(args: string[]) {\n\t// Parse CLI flags\n\tconst flags = parseFlags(args);\n\tconst filePatterns = args.filter((a) => !a.startsWith('--'));\n\n\t// Load config file if it exists\n\tconst userConfig = await loadConfig();\n\tconst config = resolveConfig(userConfig);\n\n\t// Apply CLI overrides\n\tif (flags.headed || flags.headless === false) {\n\t\tconfig.headless = false;\n\t}\n\tif (flags.browser) {\n\t\tconfig.browser = flags.browser as any;\n\t}\n\tif (flags.workers !== undefined) {\n\t\tconfig.workers = flags.workers;\n\t}\n\tif (flags.timeout !== undefined) {\n\t\tconfig.timeout = flags.timeout;\n\t}\n\tif (flags.retries !== undefined) {\n\t\tconfig.retries = flags.retries;\n\t}\n\tif (flags.debug) {\n\t\tconfig.debug = true;\n\t}\n\n\t// Set up runner options\n\tconst runnerOptions: RunnerOptions = {\n\t\tconfig,\n\t\tfiles: filePatterns.length > 0 ? filePatterns : undefined,\n\t\tgrep: flags.grep,\n\t\tbail: flags.bail,\n\t};\n\n\t// Run tests\n\tconst runner = new TestRunner(runnerOptions);\n\n\t// loadFile callback: imports the test file and returns registered tests\n\tconst loadFile = async (file: string): Promise<RunnableTest[]> => {\n\t\tconst startIdx = testRegistry.length;\n\n\t\t// For TypeScript files, register a TypeScript loader if available\n\t\tif (file.endsWith('.ts') || file.endsWith('.mts')) {\n\t\t\tawait ensureTypeScriptLoader();\n\t\t}\n\n\t\tconst fileUrl = pathToFileURL(file).href;\n\t\tawait import(fileUrl);\n\t\treturn testRegistry.slice(startIdx).map((tc) => ({\n\t\t\ttitle: tc.title,\n\t\t\tsuitePath: tc.suitePath,\n\t\t\tskip: tc.skip,\n\t\t\tonly: tc.only,\n\t\t\toptions: tc.options,\n\t\t\tfn: tc.fn as (fixtures: unknown) => Promise<void>,\n\t\t}));\n\t};\n\n\t// Launch a shared browser for all tests\n\tlet sharedBrowser: Browser | undefined;\n\n\ttry {\n\t\tsharedBrowser = await Browser.launch({\n\t\t\tbrowser: config.browser,\n\t\t\theadless: config.headless,\n\t\t\texecutablePath: config.executablePath,\n\t\t\tdebug: config.debug,\n\t\t\ttimeout: config.timeout,\n\t\t});\n\t} catch (err) {\n\t\tconsole.error(`Failed to launch browser: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t}\n\n\t// executeTest callback: runs a single test with fixture setup/teardown\n\tconst executeTest = async (test: RunnableTest) => {\n\t\treturn runTest(test as unknown as TestCase, sharedBrowser);\n\t};\n\n\ttry {\n\t\tconst exitCode = await runner.run(loadFile, executeTest);\n\t\tawait sharedBrowser.close().catch(() => {});\n\t\tprocess.exit(exitCode);\n\t} catch (err) {\n\t\tawait sharedBrowser?.close().catch(() => {});\n\t\tthrow err;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Init Command\n// ---------------------------------------------------------------------------\n\nasync function initProject() {\n\tconst cwd = process.cwd();\n\n\tconsole.log('\\n Browsecraft - Project Setup\\n');\n\n\t// Create config file\n\tconst configPath = join(cwd, 'browsecraft.config.ts');\n\tif (!existsSync(configPath)) {\n\t\twriteFileSync(\n\t\t\tconfigPath,\n\t\t\t`import { defineConfig } from 'browsecraft';\n\nexport default defineConfig({\n // Browser to use: 'chrome' | 'firefox' | 'edge'\n browser: 'chrome',\n\n // Run tests in headless mode\n headless: true,\n\n // Base URL for page.goto() calls\n // baseURL: 'http://localhost:3000',\n\n // Global timeout for actions (ms)\n timeout: 30_000,\n\n // Take screenshots on failure\n screenshot: 'on-failure',\n});\n`,\n\t\t);\n\t\tconsole.log(' Created browsecraft.config.ts');\n\t} else {\n\t\tconsole.log(' browsecraft.config.ts already exists, skipping');\n\t}\n\n\t// Create example test\n\tconst testsDir = join(cwd, 'tests');\n\tif (!existsSync(testsDir)) {\n\t\tmkdirSync(testsDir, { recursive: true });\n\t}\n\n\tconst exampleTest = join(testsDir, 'example.test.ts');\n\tif (!existsSync(exampleTest)) {\n\t\twriteFileSync(\n\t\t\texampleTest,\n\t\t\t`import { test, expect } from 'browsecraft';\n\ntest('homepage has correct title', async ({ page }) => {\n await page.goto('https://example.com');\n await expect(page).toHaveTitle('Example Domain');\n});\n\ntest('can navigate to more info', async ({ page }) => {\n await page.goto('https://example.com');\n await page.click('More information');\n await expect(page).toHaveURL(/iana\\\\.org/);\n});\n`,\n\t\t);\n\t\tconsole.log(' Created tests/example.test.ts');\n\t} else {\n\t\tconsole.log(' tests/example.test.ts already exists, skipping');\n\t}\n\n\t// Add .browsecraft to .gitignore\n\tconst gitignorePath = join(cwd, '.gitignore');\n\tif (existsSync(gitignorePath)) {\n\t\tconst content = await import('node:fs').then((fs) => fs.readFileSync(gitignorePath, 'utf-8'));\n\t\tif (!content.includes('.browsecraft')) {\n\t\t\twriteFileSync(gitignorePath, `${content.trimEnd()}\\n\\n# Browsecraft\\n.browsecraft/\\n`);\n\t\t\tconsole.log(' Updated .gitignore');\n\t\t}\n\t}\n\n\tconsole.log('\\n Setup complete! Run your first test:\\n');\n\tconsole.log(' npx browsecraft test\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Config Loading\n// ---------------------------------------------------------------------------\n\nasync function loadConfig(): Promise<UserConfig | undefined> {\n\tconst cwd = process.cwd();\n\tconst candidates = [\n\t\t'browsecraft.config.ts',\n\t\t'browsecraft.config.js',\n\t\t'browsecraft.config.mjs',\n\t\t'browsecraft.config.mts',\n\t];\n\n\tfor (const name of candidates) {\n\t\tconst configPath = resolve(cwd, name);\n\t\tif (existsSync(configPath)) {\n\t\t\ttry {\n\t\t\t\t// For .ts files, ensure a TypeScript loader is registered\n\t\t\t\tif (name.endsWith('.ts') || name.endsWith('.mts')) {\n\t\t\t\t\tawait ensureTypeScriptLoader();\n\t\t\t\t}\n\t\t\t\tconst fileUrl = pathToFileURL(configPath).href;\n\t\t\t\tconst mod = await import(fileUrl);\n\t\t\t\treturn mod.default ?? mod;\n\t\t\t} catch {\n\t\t\t\t// Config file exists but couldn't be loaded -- continue with defaults\n\t\t\t\tconsole.warn(`Warning: Could not load ${name}. Using defaults.`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n// ---------------------------------------------------------------------------\n// TypeScript Loader\n// ---------------------------------------------------------------------------\n\nlet tsLoaderRegistered = false;\n\n/**\n * Ensure a TypeScript loader is registered so that .ts test files can be imported.\n * Tries tsx first (fastest), then ts-node, then falls back to a helpful error.\n *\n * Resolution note: tsx is installed in the USER's project, not in browsecraft itself.\n * We must resolve it from the user's cwd using createRequire, not from our package.\n */\nasync function ensureTypeScriptLoader(): Promise<void> {\n\tif (tsLoaderRegistered) return;\n\n\t// Check if we're already running under a TS loader (e.g., `tsx`, `ts-node`, `bun`)\n\t// In that case, .ts imports already work\n\tconst execArgs = process.execArgv.join(' ');\n\tif (\n\t\texecArgs.includes('tsx') ||\n\t\texecArgs.includes('ts-node') ||\n\t\texecArgs.includes('loader') ||\n\t\tprocess.versions.bun // Bun handles TS natively\n\t) {\n\t\ttsLoaderRegistered = true;\n\t\treturn;\n\t}\n\n\t// Create a require function rooted at the user's cwd so we can find tsx/ts-node\n\t// installed in the user's project (not in our package)\n\tconst { createRequire } = await import('node:module');\n\tconst userRequire = createRequire(pathToFileURL(resolve(process.cwd(), 'package.json')));\n\n\t// Strategy 1: tsx 4.x — use the tsx/esm/api register() function\n\t// This is the modern approach that works with tsx 4.x+\n\ttry {\n\t\tconst tsxApiPath = userRequire.resolve('tsx/esm/api');\n\t\tconst { register } = await import(pathToFileURL(tsxApiPath).href);\n\t\tif (typeof register === 'function') {\n\t\t\tregister();\n\t\t\ttsLoaderRegistered = true;\n\t\t\treturn;\n\t\t}\n\t} catch {\n\t\t// tsx/esm/api not available, try next strategy\n\t}\n\n\t// Strategy 2: Older tsx/ts-node — use Node.js module.register()\n\t// Works with tsx <4.x and ts-node\n\tfor (const loader of ['tsx/esm', 'ts-node/esm']) {\n\t\ttry {\n\t\t\tconst { register } = await import('node:module');\n\t\t\tif (typeof register === 'function') {\n\t\t\t\t// Resolve from user's project root so the loader is found\n\t\t\t\tregister(loader, pathToFileURL(resolve(process.cwd(), '/')));\n\t\t\t\ttsLoaderRegistered = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Loader not available, try next\n\t\t}\n\t}\n\n\t// If neither tsx nor ts-node is available, give a helpful error\n\tconsole.error(\n\t\t'\\n Error: Cannot import TypeScript test files.\\n' +\n\t\t\t' Install tsx (recommended) or ts-node:\\n\\n' +\n\t\t\t' npm install -D tsx\\n\\n' +\n\t\t\t' Or run browsecraft with tsx:\\n\\n' +\n\t\t\t' npx tsx node_modules/.bin/browsecraft test\\n',\n\t);\n\tprocess.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Flag parsing\n// ---------------------------------------------------------------------------\n\ninterface CLIFlags {\n\theaded?: boolean;\n\theadless?: boolean;\n\tbrowser?: string;\n\tworkers?: number;\n\ttimeout?: number;\n\tretries?: number;\n\tgrep?: string;\n\tbail?: boolean;\n\tdebug?: boolean;\n}\n\nfunction parseFlags(args: string[]): CLIFlags {\n\tconst flags: CLIFlags = {};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i]!;\n\t\tswitch (arg) {\n\t\t\tcase '--headed':\n\t\t\t\tflags.headed = true;\n\t\t\t\tbreak;\n\t\t\tcase '--headless':\n\t\t\t\tflags.headless = true;\n\t\t\t\tbreak;\n\t\t\tcase '--debug':\n\t\t\t\tflags.debug = true;\n\t\t\t\tbreak;\n\t\t\tcase '--bail':\n\t\t\t\tflags.bail = true;\n\t\t\t\tbreak;\n\t\t\tcase '--browser':\n\t\t\t\tflags.browser = args[++i];\n\t\t\t\tbreak;\n\t\t\tcase '--workers':\n\t\t\t\tflags.workers = Number.parseInt(args[++i] ?? '1', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--timeout':\n\t\t\t\tflags.timeout = Number.parseInt(args[++i] ?? '30000', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--retries':\n\t\t\t\tflags.retries = Number.parseInt(args[++i] ?? '0', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--grep':\n\t\t\tcase '-g':\n\t\t\t\tflags.grep = args[++i];\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn flags;\n}\n\n// ---------------------------------------------------------------------------\n// Help\n// ---------------------------------------------------------------------------\n\nfunction printHelp() {\n\tconsole.log(`\n browsecraft v${VERSION} -- AI-native browser testing\n\n Usage:\n browsecraft test [files...] [options]\n browsecraft init\n\n Commands:\n test Run browser tests\n init Create a new project with example config and test\n\n Options:\n --browser <name> Browser to use: chrome, firefox, edge (default: chrome)\n --headed Run in headed mode (show the browser)\n --headless Run in headless mode (default)\n --workers <n> Number of parallel workers (default: half CPU cores)\n --timeout <ms> Global timeout in milliseconds (default: 30000)\n --retries <n> Retry failed tests n times (default: 0)\n --grep <pattern> Only run tests matching pattern\n --bail Stop after first failure\n --debug Enable verbose debug logging\n -h, --help Show this help message\n -v, --version Show version\n\n Examples:\n browsecraft test # Run all tests\n browsecraft test tests/login.test.ts # Run specific file\n browsecraft test --headed --browser firefox\n browsecraft test --grep \"login\" --bail\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\nmain().catch((err) => {\n\tconsole.error('Fatal error:', err.message);\n\tprocess.exit(1);\n});\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join, resolve } from 'path';
|
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
6
|
import { TestRunner } from 'browsecraft-runner';
|
|
7
7
|
|
|
8
|
-
var VERSION = "0.
|
|
8
|
+
var VERSION = "0.2.0";
|
|
9
9
|
async function main() {
|
|
10
10
|
const args = process.argv.slice(2);
|
|
11
11
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
@@ -191,7 +191,11 @@ async function loadConfig() {
|
|
|
191
191
|
const configPath = resolve(cwd, name);
|
|
192
192
|
if (existsSync(configPath)) {
|
|
193
193
|
try {
|
|
194
|
-
|
|
194
|
+
if (name.endsWith(".ts") || name.endsWith(".mts")) {
|
|
195
|
+
await ensureTypeScriptLoader();
|
|
196
|
+
}
|
|
197
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
198
|
+
const mod = await import(fileUrl);
|
|
195
199
|
return mod.default ?? mod;
|
|
196
200
|
} catch {
|
|
197
201
|
console.warn(`Warning: Could not load ${name}. Using defaults.`);
|
|
@@ -208,11 +212,23 @@ async function ensureTypeScriptLoader() {
|
|
|
208
212
|
tsLoaderRegistered = true;
|
|
209
213
|
return;
|
|
210
214
|
}
|
|
215
|
+
const { createRequire } = await import('module');
|
|
216
|
+
const userRequire = createRequire(pathToFileURL(resolve(process.cwd(), "package.json")));
|
|
217
|
+
try {
|
|
218
|
+
const tsxApiPath = userRequire.resolve("tsx/esm/api");
|
|
219
|
+
const { register } = await import(pathToFileURL(tsxApiPath).href);
|
|
220
|
+
if (typeof register === "function") {
|
|
221
|
+
register();
|
|
222
|
+
tsLoaderRegistered = true;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
211
227
|
for (const loader of ["tsx/esm", "ts-node/esm"]) {
|
|
212
228
|
try {
|
|
213
229
|
const { register } = await import('module');
|
|
214
230
|
if (typeof register === "function") {
|
|
215
|
-
register(loader, pathToFileURL("
|
|
231
|
+
register(loader, pathToFileURL(resolve(process.cwd(), "/")));
|
|
216
232
|
tsLoaderRegistered = true;
|
|
217
233
|
return;
|
|
218
234
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.js","sourcesContent":["#!/usr/bin/env node\n// ============================================================================\n// Browsecraft - CLI\n// The command-line interface for running tests and initializing projects.\n//\n// npx browsecraft test # Run all tests\n// npx browsecraft test login.test.ts # Run specific file\n// npx browsecraft init # Scaffold a new project\n// npx browsecraft --help # Show help\n// ============================================================================\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { type RunnableTest, type RunnerOptions, TestRunner } from 'browsecraft-runner';\nimport { Browser } from './browser.js';\nimport { type UserConfig, resolveConfig } from './config.js';\nimport { type TestCase, runAfterAllHooks, runTest, testRegistry } from './test.js';\n\nconst VERSION = '0.1.0';\n\nasync function main() {\n\tconst args = process.argv.slice(2);\n\n\tif (args.length === 0 || args.includes('--help') || args.includes('-h')) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\tif (args.includes('--version') || args.includes('-v')) {\n\t\tconsole.log(`browsecraft v${VERSION}`);\n\t\treturn;\n\t}\n\n\tconst command = args[0];\n\n\tswitch (command) {\n\t\tcase 'test':\n\t\t\tawait runTests(args.slice(1));\n\t\t\tbreak;\n\t\tcase 'init':\n\t\t\tawait initProject();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t// If no command, assume it's a file path to test\n\t\t\tif (command && (command.endsWith('.ts') || command.endsWith('.js'))) {\n\t\t\t\tawait runTests(args);\n\t\t\t} else {\n\t\t\t\tconsole.error(`Unknown command: ${command}`);\n\t\t\t\tconsole.error('Run \"browsecraft --help\" for usage information.');\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Test Command\n// ---------------------------------------------------------------------------\n\nasync function runTests(args: string[]) {\n\t// Parse CLI flags\n\tconst flags = parseFlags(args);\n\tconst filePatterns = args.filter((a) => !a.startsWith('--'));\n\n\t// Load config file if it exists\n\tconst userConfig = await loadConfig();\n\tconst config = resolveConfig(userConfig);\n\n\t// Apply CLI overrides\n\tif (flags.headed || flags.headless === false) {\n\t\tconfig.headless = false;\n\t}\n\tif (flags.browser) {\n\t\tconfig.browser = flags.browser as any;\n\t}\n\tif (flags.workers !== undefined) {\n\t\tconfig.workers = flags.workers;\n\t}\n\tif (flags.timeout !== undefined) {\n\t\tconfig.timeout = flags.timeout;\n\t}\n\tif (flags.retries !== undefined) {\n\t\tconfig.retries = flags.retries;\n\t}\n\tif (flags.debug) {\n\t\tconfig.debug = true;\n\t}\n\n\t// Set up runner options\n\tconst runnerOptions: RunnerOptions = {\n\t\tconfig,\n\t\tfiles: filePatterns.length > 0 ? filePatterns : undefined,\n\t\tgrep: flags.grep,\n\t\tbail: flags.bail,\n\t};\n\n\t// Run tests\n\tconst runner = new TestRunner(runnerOptions);\n\n\t// loadFile callback: imports the test file and returns registered tests\n\tconst loadFile = async (file: string): Promise<RunnableTest[]> => {\n\t\tconst startIdx = testRegistry.length;\n\n\t\t// For TypeScript files, register a TypeScript loader if available\n\t\tif (file.endsWith('.ts') || file.endsWith('.mts')) {\n\t\t\tawait ensureTypeScriptLoader();\n\t\t}\n\n\t\tconst fileUrl = pathToFileURL(file).href;\n\t\tawait import(fileUrl);\n\t\treturn testRegistry.slice(startIdx).map((tc) => ({\n\t\t\ttitle: tc.title,\n\t\t\tsuitePath: tc.suitePath,\n\t\t\tskip: tc.skip,\n\t\t\tonly: tc.only,\n\t\t\toptions: tc.options,\n\t\t\tfn: tc.fn as (fixtures: unknown) => Promise<void>,\n\t\t}));\n\t};\n\n\t// Launch a shared browser for all tests\n\tlet sharedBrowser: Browser | undefined;\n\n\ttry {\n\t\tsharedBrowser = await Browser.launch({\n\t\t\tbrowser: config.browser,\n\t\t\theadless: config.headless,\n\t\t\texecutablePath: config.executablePath,\n\t\t\tdebug: config.debug,\n\t\t\ttimeout: config.timeout,\n\t\t});\n\t} catch (err) {\n\t\tconsole.error(`Failed to launch browser: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t}\n\n\t// executeTest callback: runs a single test with fixture setup/teardown\n\tconst executeTest = async (test: RunnableTest) => {\n\t\treturn runTest(test as unknown as TestCase, sharedBrowser);\n\t};\n\n\ttry {\n\t\tconst exitCode = await runner.run(loadFile, executeTest);\n\t\tawait sharedBrowser.close().catch(() => {});\n\t\tprocess.exit(exitCode);\n\t} catch (err) {\n\t\tawait sharedBrowser?.close().catch(() => {});\n\t\tthrow err;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Init Command\n// ---------------------------------------------------------------------------\n\nasync function initProject() {\n\tconst cwd = process.cwd();\n\n\tconsole.log('\\n Browsecraft - Project Setup\\n');\n\n\t// Create config file\n\tconst configPath = join(cwd, 'browsecraft.config.ts');\n\tif (!existsSync(configPath)) {\n\t\twriteFileSync(\n\t\t\tconfigPath,\n\t\t\t`import { defineConfig } from 'browsecraft';\n\nexport default defineConfig({\n // Browser to use: 'chrome' | 'firefox' | 'edge'\n browser: 'chrome',\n\n // Run tests in headless mode\n headless: true,\n\n // Base URL for page.goto() calls\n // baseURL: 'http://localhost:3000',\n\n // Global timeout for actions (ms)\n timeout: 30_000,\n\n // Take screenshots on failure\n screenshot: 'on-failure',\n});\n`,\n\t\t);\n\t\tconsole.log(' Created browsecraft.config.ts');\n\t} else {\n\t\tconsole.log(' browsecraft.config.ts already exists, skipping');\n\t}\n\n\t// Create example test\n\tconst testsDir = join(cwd, 'tests');\n\tif (!existsSync(testsDir)) {\n\t\tmkdirSync(testsDir, { recursive: true });\n\t}\n\n\tconst exampleTest = join(testsDir, 'example.test.ts');\n\tif (!existsSync(exampleTest)) {\n\t\twriteFileSync(\n\t\t\texampleTest,\n\t\t\t`import { test, expect } from 'browsecraft';\n\ntest('homepage has correct title', async ({ page }) => {\n await page.goto('https://example.com');\n await expect(page).toHaveTitle('Example Domain');\n});\n\ntest('can navigate to more info', async ({ page }) => {\n await page.goto('https://example.com');\n await page.click('More information');\n await expect(page).toHaveURL(/iana\\\\.org/);\n});\n`,\n\t\t);\n\t\tconsole.log(' Created tests/example.test.ts');\n\t} else {\n\t\tconsole.log(' tests/example.test.ts already exists, skipping');\n\t}\n\n\t// Add .browsecraft to .gitignore\n\tconst gitignorePath = join(cwd, '.gitignore');\n\tif (existsSync(gitignorePath)) {\n\t\tconst content = await import('node:fs').then((fs) => fs.readFileSync(gitignorePath, 'utf-8'));\n\t\tif (!content.includes('.browsecraft')) {\n\t\t\twriteFileSync(gitignorePath, `${content.trimEnd()}\\n\\n# Browsecraft\\n.browsecraft/\\n`);\n\t\t\tconsole.log(' Updated .gitignore');\n\t\t}\n\t}\n\n\tconsole.log('\\n Setup complete! Run your first test:\\n');\n\tconsole.log(' npx browsecraft test\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Config Loading\n// ---------------------------------------------------------------------------\n\nasync function loadConfig(): Promise<UserConfig | undefined> {\n\tconst cwd = process.cwd();\n\tconst candidates = [\n\t\t'browsecraft.config.ts',\n\t\t'browsecraft.config.js',\n\t\t'browsecraft.config.mjs',\n\t\t'browsecraft.config.mts',\n\t];\n\n\tfor (const name of candidates) {\n\t\tconst configPath = resolve(cwd, name);\n\t\tif (existsSync(configPath)) {\n\t\t\ttry {\n\t\t\t\t// For .ts files, we need tsx or ts-node to be available\n\t\t\t\tconst mod = await import(configPath);\n\t\t\t\treturn mod.default ?? mod;\n\t\t\t} catch {\n\t\t\t\t// Config file exists but couldn't be loaded -- continue with defaults\n\t\t\t\tconsole.warn(`Warning: Could not load ${name}. Using defaults.`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n// ---------------------------------------------------------------------------\n// TypeScript Loader\n// ---------------------------------------------------------------------------\n\nlet tsLoaderRegistered = false;\n\n/**\n * Ensure a TypeScript loader is registered so that .ts test files can be imported.\n * Tries tsx first (fastest), then ts-node, then falls back to a helpful error.\n */\nasync function ensureTypeScriptLoader(): Promise<void> {\n\tif (tsLoaderRegistered) return;\n\n\t// Check if we're already running under a TS loader (e.g., `tsx`, `ts-node`, `bun`)\n\t// In that case, .ts imports already work\n\tconst execArgs = process.execArgv.join(' ');\n\tif (\n\t\texecArgs.includes('tsx') ||\n\t\texecArgs.includes('ts-node') ||\n\t\texecArgs.includes('loader') ||\n\t\tprocess.versions.bun // Bun handles TS natively\n\t) {\n\t\ttsLoaderRegistered = true;\n\t\treturn;\n\t}\n\n\t// Try to dynamically register tsx or ts-node\n\tfor (const loader of ['tsx/esm', 'ts-node/esm']) {\n\t\ttry {\n\t\t\t// Node 20.6+ supports module.register()\n\t\t\tconst { register } = await import('node:module');\n\t\t\tif (typeof register === 'function') {\n\t\t\t\tregister(loader, pathToFileURL('./'));\n\t\t\t\ttsLoaderRegistered = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Loader not available, try next\n\t\t}\n\t}\n\n\t// If neither tsx nor ts-node is available, give a helpful error\n\tconsole.error(\n\t\t'\\n Error: Cannot import TypeScript test files.\\n' +\n\t\t\t' Install tsx (recommended) or ts-node:\\n\\n' +\n\t\t\t' npm install -D tsx\\n\\n' +\n\t\t\t' Or run browsecraft with tsx:\\n\\n' +\n\t\t\t' npx tsx node_modules/.bin/browsecraft test\\n',\n\t);\n\tprocess.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Flag parsing\n// ---------------------------------------------------------------------------\n\ninterface CLIFlags {\n\theaded?: boolean;\n\theadless?: boolean;\n\tbrowser?: string;\n\tworkers?: number;\n\ttimeout?: number;\n\tretries?: number;\n\tgrep?: string;\n\tbail?: boolean;\n\tdebug?: boolean;\n}\n\nfunction parseFlags(args: string[]): CLIFlags {\n\tconst flags: CLIFlags = {};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i]!;\n\t\tswitch (arg) {\n\t\t\tcase '--headed':\n\t\t\t\tflags.headed = true;\n\t\t\t\tbreak;\n\t\t\tcase '--headless':\n\t\t\t\tflags.headless = true;\n\t\t\t\tbreak;\n\t\t\tcase '--debug':\n\t\t\t\tflags.debug = true;\n\t\t\t\tbreak;\n\t\t\tcase '--bail':\n\t\t\t\tflags.bail = true;\n\t\t\t\tbreak;\n\t\t\tcase '--browser':\n\t\t\t\tflags.browser = args[++i];\n\t\t\t\tbreak;\n\t\t\tcase '--workers':\n\t\t\t\tflags.workers = Number.parseInt(args[++i] ?? '1', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--timeout':\n\t\t\t\tflags.timeout = Number.parseInt(args[++i] ?? '30000', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--retries':\n\t\t\t\tflags.retries = Number.parseInt(args[++i] ?? '0', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--grep':\n\t\t\tcase '-g':\n\t\t\t\tflags.grep = args[++i];\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn flags;\n}\n\n// ---------------------------------------------------------------------------\n// Help\n// ---------------------------------------------------------------------------\n\nfunction printHelp() {\n\tconsole.log(`\n browsecraft v${VERSION} -- AI-native browser testing\n\n Usage:\n browsecraft test [files...] [options]\n browsecraft init\n\n Commands:\n test Run browser tests\n init Create a new project with example config and test\n\n Options:\n --browser <name> Browser to use: chrome, firefox, edge (default: chrome)\n --headed Run in headed mode (show the browser)\n --headless Run in headless mode (default)\n --workers <n> Number of parallel workers (default: half CPU cores)\n --timeout <ms> Global timeout in milliseconds (default: 30000)\n --retries <n> Retry failed tests n times (default: 0)\n --grep <pattern> Only run tests matching pattern\n --bail Stop after first failure\n --debug Enable verbose debug logging\n -h, --help Show this help message\n -v, --version Show version\n\n Examples:\n browsecraft test # Run all tests\n browsecraft test tests/login.test.ts # Run specific file\n browsecraft test --headed --browser firefox\n browsecraft test --grep \"login\" --bail\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\nmain().catch((err) => {\n\tconsole.error('Fatal error:', err.message);\n\tprocess.exit(1);\n});\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.js","sourcesContent":["#!/usr/bin/env node\n// ============================================================================\n// Browsecraft - CLI\n// The command-line interface for running tests and initializing projects.\n//\n// npx browsecraft test # Run all tests\n// npx browsecraft test login.test.ts # Run specific file\n// npx browsecraft init # Scaffold a new project\n// npx browsecraft --help # Show help\n// ============================================================================\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { type RunnableTest, type RunnerOptions, TestRunner } from 'browsecraft-runner';\nimport { Browser } from './browser.js';\nimport { type UserConfig, resolveConfig } from './config.js';\nimport { type TestCase, runAfterAllHooks, runTest, testRegistry } from './test.js';\n\nconst VERSION = '0.2.0';\n\nasync function main() {\n\tconst args = process.argv.slice(2);\n\n\tif (args.length === 0 || args.includes('--help') || args.includes('-h')) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\tif (args.includes('--version') || args.includes('-v')) {\n\t\tconsole.log(`browsecraft v${VERSION}`);\n\t\treturn;\n\t}\n\n\tconst command = args[0];\n\n\tswitch (command) {\n\t\tcase 'test':\n\t\t\tawait runTests(args.slice(1));\n\t\t\tbreak;\n\t\tcase 'init':\n\t\t\tawait initProject();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t// If no command, assume it's a file path to test\n\t\t\tif (command && (command.endsWith('.ts') || command.endsWith('.js'))) {\n\t\t\t\tawait runTests(args);\n\t\t\t} else {\n\t\t\t\tconsole.error(`Unknown command: ${command}`);\n\t\t\t\tconsole.error('Run \"browsecraft --help\" for usage information.');\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Test Command\n// ---------------------------------------------------------------------------\n\nasync function runTests(args: string[]) {\n\t// Parse CLI flags\n\tconst flags = parseFlags(args);\n\tconst filePatterns = args.filter((a) => !a.startsWith('--'));\n\n\t// Load config file if it exists\n\tconst userConfig = await loadConfig();\n\tconst config = resolveConfig(userConfig);\n\n\t// Apply CLI overrides\n\tif (flags.headed || flags.headless === false) {\n\t\tconfig.headless = false;\n\t}\n\tif (flags.browser) {\n\t\tconfig.browser = flags.browser as any;\n\t}\n\tif (flags.workers !== undefined) {\n\t\tconfig.workers = flags.workers;\n\t}\n\tif (flags.timeout !== undefined) {\n\t\tconfig.timeout = flags.timeout;\n\t}\n\tif (flags.retries !== undefined) {\n\t\tconfig.retries = flags.retries;\n\t}\n\tif (flags.debug) {\n\t\tconfig.debug = true;\n\t}\n\n\t// Set up runner options\n\tconst runnerOptions: RunnerOptions = {\n\t\tconfig,\n\t\tfiles: filePatterns.length > 0 ? filePatterns : undefined,\n\t\tgrep: flags.grep,\n\t\tbail: flags.bail,\n\t};\n\n\t// Run tests\n\tconst runner = new TestRunner(runnerOptions);\n\n\t// loadFile callback: imports the test file and returns registered tests\n\tconst loadFile = async (file: string): Promise<RunnableTest[]> => {\n\t\tconst startIdx = testRegistry.length;\n\n\t\t// For TypeScript files, register a TypeScript loader if available\n\t\tif (file.endsWith('.ts') || file.endsWith('.mts')) {\n\t\t\tawait ensureTypeScriptLoader();\n\t\t}\n\n\t\tconst fileUrl = pathToFileURL(file).href;\n\t\tawait import(fileUrl);\n\t\treturn testRegistry.slice(startIdx).map((tc) => ({\n\t\t\ttitle: tc.title,\n\t\t\tsuitePath: tc.suitePath,\n\t\t\tskip: tc.skip,\n\t\t\tonly: tc.only,\n\t\t\toptions: tc.options,\n\t\t\tfn: tc.fn as (fixtures: unknown) => Promise<void>,\n\t\t}));\n\t};\n\n\t// Launch a shared browser for all tests\n\tlet sharedBrowser: Browser | undefined;\n\n\ttry {\n\t\tsharedBrowser = await Browser.launch({\n\t\t\tbrowser: config.browser,\n\t\t\theadless: config.headless,\n\t\t\texecutablePath: config.executablePath,\n\t\t\tdebug: config.debug,\n\t\t\ttimeout: config.timeout,\n\t\t});\n\t} catch (err) {\n\t\tconsole.error(`Failed to launch browser: ${err instanceof Error ? err.message : String(err)}`);\n\t\tprocess.exit(1);\n\t}\n\n\t// executeTest callback: runs a single test with fixture setup/teardown\n\tconst executeTest = async (test: RunnableTest) => {\n\t\treturn runTest(test as unknown as TestCase, sharedBrowser);\n\t};\n\n\ttry {\n\t\tconst exitCode = await runner.run(loadFile, executeTest);\n\t\tawait sharedBrowser.close().catch(() => {});\n\t\tprocess.exit(exitCode);\n\t} catch (err) {\n\t\tawait sharedBrowser?.close().catch(() => {});\n\t\tthrow err;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Init Command\n// ---------------------------------------------------------------------------\n\nasync function initProject() {\n\tconst cwd = process.cwd();\n\n\tconsole.log('\\n Browsecraft - Project Setup\\n');\n\n\t// Create config file\n\tconst configPath = join(cwd, 'browsecraft.config.ts');\n\tif (!existsSync(configPath)) {\n\t\twriteFileSync(\n\t\t\tconfigPath,\n\t\t\t`import { defineConfig } from 'browsecraft';\n\nexport default defineConfig({\n // Browser to use: 'chrome' | 'firefox' | 'edge'\n browser: 'chrome',\n\n // Run tests in headless mode\n headless: true,\n\n // Base URL for page.goto() calls\n // baseURL: 'http://localhost:3000',\n\n // Global timeout for actions (ms)\n timeout: 30_000,\n\n // Take screenshots on failure\n screenshot: 'on-failure',\n});\n`,\n\t\t);\n\t\tconsole.log(' Created browsecraft.config.ts');\n\t} else {\n\t\tconsole.log(' browsecraft.config.ts already exists, skipping');\n\t}\n\n\t// Create example test\n\tconst testsDir = join(cwd, 'tests');\n\tif (!existsSync(testsDir)) {\n\t\tmkdirSync(testsDir, { recursive: true });\n\t}\n\n\tconst exampleTest = join(testsDir, 'example.test.ts');\n\tif (!existsSync(exampleTest)) {\n\t\twriteFileSync(\n\t\t\texampleTest,\n\t\t\t`import { test, expect } from 'browsecraft';\n\ntest('homepage has correct title', async ({ page }) => {\n await page.goto('https://example.com');\n await expect(page).toHaveTitle('Example Domain');\n});\n\ntest('can navigate to more info', async ({ page }) => {\n await page.goto('https://example.com');\n await page.click('More information');\n await expect(page).toHaveURL(/iana\\\\.org/);\n});\n`,\n\t\t);\n\t\tconsole.log(' Created tests/example.test.ts');\n\t} else {\n\t\tconsole.log(' tests/example.test.ts already exists, skipping');\n\t}\n\n\t// Add .browsecraft to .gitignore\n\tconst gitignorePath = join(cwd, '.gitignore');\n\tif (existsSync(gitignorePath)) {\n\t\tconst content = await import('node:fs').then((fs) => fs.readFileSync(gitignorePath, 'utf-8'));\n\t\tif (!content.includes('.browsecraft')) {\n\t\t\twriteFileSync(gitignorePath, `${content.trimEnd()}\\n\\n# Browsecraft\\n.browsecraft/\\n`);\n\t\t\tconsole.log(' Updated .gitignore');\n\t\t}\n\t}\n\n\tconsole.log('\\n Setup complete! Run your first test:\\n');\n\tconsole.log(' npx browsecraft test\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Config Loading\n// ---------------------------------------------------------------------------\n\nasync function loadConfig(): Promise<UserConfig | undefined> {\n\tconst cwd = process.cwd();\n\tconst candidates = [\n\t\t'browsecraft.config.ts',\n\t\t'browsecraft.config.js',\n\t\t'browsecraft.config.mjs',\n\t\t'browsecraft.config.mts',\n\t];\n\n\tfor (const name of candidates) {\n\t\tconst configPath = resolve(cwd, name);\n\t\tif (existsSync(configPath)) {\n\t\t\ttry {\n\t\t\t\t// For .ts files, ensure a TypeScript loader is registered\n\t\t\t\tif (name.endsWith('.ts') || name.endsWith('.mts')) {\n\t\t\t\t\tawait ensureTypeScriptLoader();\n\t\t\t\t}\n\t\t\t\tconst fileUrl = pathToFileURL(configPath).href;\n\t\t\t\tconst mod = await import(fileUrl);\n\t\t\t\treturn mod.default ?? mod;\n\t\t\t} catch {\n\t\t\t\t// Config file exists but couldn't be loaded -- continue with defaults\n\t\t\t\tconsole.warn(`Warning: Could not load ${name}. Using defaults.`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n// ---------------------------------------------------------------------------\n// TypeScript Loader\n// ---------------------------------------------------------------------------\n\nlet tsLoaderRegistered = false;\n\n/**\n * Ensure a TypeScript loader is registered so that .ts test files can be imported.\n * Tries tsx first (fastest), then ts-node, then falls back to a helpful error.\n *\n * Resolution note: tsx is installed in the USER's project, not in browsecraft itself.\n * We must resolve it from the user's cwd using createRequire, not from our package.\n */\nasync function ensureTypeScriptLoader(): Promise<void> {\n\tif (tsLoaderRegistered) return;\n\n\t// Check if we're already running under a TS loader (e.g., `tsx`, `ts-node`, `bun`)\n\t// In that case, .ts imports already work\n\tconst execArgs = process.execArgv.join(' ');\n\tif (\n\t\texecArgs.includes('tsx') ||\n\t\texecArgs.includes('ts-node') ||\n\t\texecArgs.includes('loader') ||\n\t\tprocess.versions.bun // Bun handles TS natively\n\t) {\n\t\ttsLoaderRegistered = true;\n\t\treturn;\n\t}\n\n\t// Create a require function rooted at the user's cwd so we can find tsx/ts-node\n\t// installed in the user's project (not in our package)\n\tconst { createRequire } = await import('node:module');\n\tconst userRequire = createRequire(pathToFileURL(resolve(process.cwd(), 'package.json')));\n\n\t// Strategy 1: tsx 4.x — use the tsx/esm/api register() function\n\t// This is the modern approach that works with tsx 4.x+\n\ttry {\n\t\tconst tsxApiPath = userRequire.resolve('tsx/esm/api');\n\t\tconst { register } = await import(pathToFileURL(tsxApiPath).href);\n\t\tif (typeof register === 'function') {\n\t\t\tregister();\n\t\t\ttsLoaderRegistered = true;\n\t\t\treturn;\n\t\t}\n\t} catch {\n\t\t// tsx/esm/api not available, try next strategy\n\t}\n\n\t// Strategy 2: Older tsx/ts-node — use Node.js module.register()\n\t// Works with tsx <4.x and ts-node\n\tfor (const loader of ['tsx/esm', 'ts-node/esm']) {\n\t\ttry {\n\t\t\tconst { register } = await import('node:module');\n\t\t\tif (typeof register === 'function') {\n\t\t\t\t// Resolve from user's project root so the loader is found\n\t\t\t\tregister(loader, pathToFileURL(resolve(process.cwd(), '/')));\n\t\t\t\ttsLoaderRegistered = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Loader not available, try next\n\t\t}\n\t}\n\n\t// If neither tsx nor ts-node is available, give a helpful error\n\tconsole.error(\n\t\t'\\n Error: Cannot import TypeScript test files.\\n' +\n\t\t\t' Install tsx (recommended) or ts-node:\\n\\n' +\n\t\t\t' npm install -D tsx\\n\\n' +\n\t\t\t' Or run browsecraft with tsx:\\n\\n' +\n\t\t\t' npx tsx node_modules/.bin/browsecraft test\\n',\n\t);\n\tprocess.exit(1);\n}\n\n// ---------------------------------------------------------------------------\n// Flag parsing\n// ---------------------------------------------------------------------------\n\ninterface CLIFlags {\n\theaded?: boolean;\n\theadless?: boolean;\n\tbrowser?: string;\n\tworkers?: number;\n\ttimeout?: number;\n\tretries?: number;\n\tgrep?: string;\n\tbail?: boolean;\n\tdebug?: boolean;\n}\n\nfunction parseFlags(args: string[]): CLIFlags {\n\tconst flags: CLIFlags = {};\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i]!;\n\t\tswitch (arg) {\n\t\t\tcase '--headed':\n\t\t\t\tflags.headed = true;\n\t\t\t\tbreak;\n\t\t\tcase '--headless':\n\t\t\t\tflags.headless = true;\n\t\t\t\tbreak;\n\t\t\tcase '--debug':\n\t\t\t\tflags.debug = true;\n\t\t\t\tbreak;\n\t\t\tcase '--bail':\n\t\t\t\tflags.bail = true;\n\t\t\t\tbreak;\n\t\t\tcase '--browser':\n\t\t\t\tflags.browser = args[++i];\n\t\t\t\tbreak;\n\t\t\tcase '--workers':\n\t\t\t\tflags.workers = Number.parseInt(args[++i] ?? '1', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--timeout':\n\t\t\t\tflags.timeout = Number.parseInt(args[++i] ?? '30000', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--retries':\n\t\t\t\tflags.retries = Number.parseInt(args[++i] ?? '0', 10);\n\t\t\t\tbreak;\n\t\t\tcase '--grep':\n\t\t\tcase '-g':\n\t\t\t\tflags.grep = args[++i];\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn flags;\n}\n\n// ---------------------------------------------------------------------------\n// Help\n// ---------------------------------------------------------------------------\n\nfunction printHelp() {\n\tconsole.log(`\n browsecraft v${VERSION} -- AI-native browser testing\n\n Usage:\n browsecraft test [files...] [options]\n browsecraft init\n\n Commands:\n test Run browser tests\n init Create a new project with example config and test\n\n Options:\n --browser <name> Browser to use: chrome, firefox, edge (default: chrome)\n --headed Run in headed mode (show the browser)\n --headless Run in headless mode (default)\n --workers <n> Number of parallel workers (default: half CPU cores)\n --timeout <ms> Global timeout in milliseconds (default: 30000)\n --retries <n> Retry failed tests n times (default: 0)\n --grep <pattern> Only run tests matching pattern\n --bail Stop after first failure\n --debug Enable verbose debug logging\n -h, --help Show this help message\n -v, --version Show version\n\n Examples:\n browsecraft test # Run all tests\n browsecraft test tests/login.test.ts # Run specific file\n browsecraft test --headed --browser firefox\n browsecraft test --grep \"login\" --bail\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\nmain().catch((err) => {\n\tconsole.error('Fatal error:', err.message);\n\tprocess.exit(1);\n});\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browsecraft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "AI-native browser automation framework. Craft browser tests that just work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"browsecraft-
|
|
29
|
-
"browsecraft-
|
|
30
|
-
"browsecraft-
|
|
28
|
+
"browsecraft-bidi": "0.3.0",
|
|
29
|
+
"browsecraft-bdd": "0.3.0",
|
|
30
|
+
"browsecraft-runner": "0.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^25.3.0",
|