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 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.1.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
- const mod = await import(configPath);
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":"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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.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.1.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
- const mod = await import(configPath);
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":";;;;;;;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,GAAS,cAAc,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,IAAI,UAAA,CAAW,aAAa,CAAA;AAG3C,EAAA,MAAM,QAAA,GAAW,OAAO,IAAA,KAA0C;AACjE,IAAA,MAAM,WAAW,YAAA,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,GAAU,aAAA,CAAc,IAAI,CAAA,CAAE,IAAA;AACpC,IAAA,MAAM,OAAO,OAAA,CAAA;AACb,IAAA,OAAO,aAAa,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,MAAM,QAAQ,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,OAAO,OAAA,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,GAAa,IAAA,CAAK,GAAA,EAAK,uBAAuB,CAAA;AACpD,EAAA,IAAI,CAAC,UAAA,CAAW,UAAU,CAAA,EAAG;AAC5B,IAAA,aAAA;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,GAAW,IAAA,CAAK,GAAA,EAAK,OAAO,CAAA;AAClC,EAAA,IAAI,CAAC,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC1B,IAAA,SAAA,CAAU,QAAA,EAAU,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,QAAA,EAAU,iBAAiB,CAAA;AACpD,EAAA,IAAI,CAAC,UAAA,CAAW,WAAW,CAAA,EAAG;AAC7B,IAAA,aAAA;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,GAAgB,IAAA,CAAK,GAAA,EAAK,YAAY,CAAA;AAC5C,EAAA,IAAI,UAAA,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,MAAA,aAAA,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,GAAa,OAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AACpC,IAAA,IAAI,UAAA,CAAW,UAAU,CAAA,EAAG;AAC3B,MAAA,IAAI;AAEH,QAAA,IAAI,KAAK,QAAA,CAAS,KAAK,KAAK,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG;AAClD,UAAA,MAAM,sBAAA,EAAuB;AAAA,QAC9B;AACA,QAAA,MAAM,OAAA,GAAU,aAAA,CAAc,UAAU,CAAA,CAAE,IAAA;AAC1C,QAAA,MAAM,GAAA,GAAM,MAAM,OAAO,OAAA,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;AASzB,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;AAIA,EAAA,MAAM,EAAE,aAAA,EAAc,GAAI,MAAM,OAAO,QAAa,CAAA;AACpD,EAAA,MAAM,WAAA,GAAc,cAAc,aAAA,CAAc,OAAA,CAAQ,QAAQ,GAAA,EAAI,EAAG,cAAc,CAAC,CAAC,CAAA;AAIvF,EAAA,IAAI;AACH,IAAA,MAAM,UAAA,GAAa,WAAA,CAAY,OAAA,CAAQ,aAAa,CAAA;AACpD,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,aAAA,CAAc,UAAU,CAAA,CAAE,IAAA,CAAA;AAC5D,IAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AACnC,MAAA,QAAA,EAAS;AACT,MAAA,kBAAA,GAAqB,IAAA;AACrB,MAAA;AAAA,IACD;AAAA,EACD,CAAA,CAAA,MAAQ;AAAA,EAER;AAIA,EAAA,KAAA,MAAW,MAAA,IAAU,CAAC,SAAA,EAAW,aAAa,CAAA,EAAG;AAChD,IAAA,IAAI;AACH,MAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,OAAO,QAAa,CAAA;AAC/C,MAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AAEnC,QAAA,QAAA,CAAS,MAAA,EAAQ,cAAc,OAAA,CAAQ,OAAA,CAAQ,KAAI,EAAG,GAAG,CAAC,CAAC,CAAA;AAC3D,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.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.2.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-bdd": "0.2.0",
29
- "browsecraft-runner": "0.2.0",
30
- "browsecraft-bidi": "0.2.0"
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",