browsecraft 0.1.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 ADDED
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var chunkKIPQFK3Y_cjs = require('./chunk-KIPQFK3Y.cjs');
5
+ var path = require('path');
6
+ var fs = require('fs');
7
+ var url = require('url');
8
+ var browsecraftRunner = require('browsecraft-runner');
9
+
10
+ var VERSION = "0.1.0";
11
+ async function main() {
12
+ const args = process.argv.slice(2);
13
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
14
+ printHelp();
15
+ return;
16
+ }
17
+ if (args.includes("--version") || args.includes("-v")) {
18
+ console.log(`browsecraft v${VERSION}`);
19
+ return;
20
+ }
21
+ const command = args[0];
22
+ switch (command) {
23
+ case "test":
24
+ await runTests(args.slice(1));
25
+ break;
26
+ case "init":
27
+ await initProject();
28
+ break;
29
+ default:
30
+ if (command && (command.endsWith(".ts") || command.endsWith(".js"))) {
31
+ await runTests(args);
32
+ } else {
33
+ console.error(`Unknown command: ${command}`);
34
+ console.error('Run "browsecraft --help" for usage information.');
35
+ process.exit(1);
36
+ }
37
+ }
38
+ }
39
+ async function runTests(args) {
40
+ const flags = parseFlags(args);
41
+ const filePatterns = args.filter((a) => !a.startsWith("--"));
42
+ const userConfig = await loadConfig();
43
+ const config = chunkKIPQFK3Y_cjs.resolveConfig(userConfig);
44
+ if (flags.headed || flags.headless === false) {
45
+ config.headless = false;
46
+ }
47
+ if (flags.browser) {
48
+ config.browser = flags.browser;
49
+ }
50
+ if (flags.workers !== void 0) {
51
+ config.workers = flags.workers;
52
+ }
53
+ if (flags.timeout !== void 0) {
54
+ config.timeout = flags.timeout;
55
+ }
56
+ if (flags.retries !== void 0) {
57
+ config.retries = flags.retries;
58
+ }
59
+ if (flags.debug) {
60
+ config.debug = true;
61
+ }
62
+ const runnerOptions = {
63
+ config,
64
+ files: filePatterns.length > 0 ? filePatterns : void 0,
65
+ grep: flags.grep,
66
+ bail: flags.bail
67
+ };
68
+ const runner = new browsecraftRunner.TestRunner(runnerOptions);
69
+ const loadFile = async (file) => {
70
+ const startIdx = chunkKIPQFK3Y_cjs.testRegistry.length;
71
+ if (file.endsWith(".ts") || file.endsWith(".mts")) {
72
+ await ensureTypeScriptLoader();
73
+ }
74
+ const fileUrl = url.pathToFileURL(file).href;
75
+ await import(fileUrl);
76
+ return chunkKIPQFK3Y_cjs.testRegistry.slice(startIdx).map((tc) => ({
77
+ title: tc.title,
78
+ suitePath: tc.suitePath,
79
+ skip: tc.skip,
80
+ only: tc.only,
81
+ options: tc.options,
82
+ fn: tc.fn
83
+ }));
84
+ };
85
+ let sharedBrowser;
86
+ try {
87
+ sharedBrowser = await chunkKIPQFK3Y_cjs.Browser.launch({
88
+ browser: config.browser,
89
+ headless: config.headless,
90
+ executablePath: config.executablePath,
91
+ debug: config.debug,
92
+ timeout: config.timeout
93
+ });
94
+ } catch (err) {
95
+ console.error(`Failed to launch browser: ${err instanceof Error ? err.message : String(err)}`);
96
+ process.exit(1);
97
+ }
98
+ const executeTest = async (test) => {
99
+ return chunkKIPQFK3Y_cjs.runTest(test, sharedBrowser);
100
+ };
101
+ try {
102
+ const exitCode = await runner.run(loadFile, executeTest);
103
+ await sharedBrowser.close().catch(() => {
104
+ });
105
+ process.exit(exitCode);
106
+ } catch (err) {
107
+ await sharedBrowser?.close().catch(() => {
108
+ });
109
+ throw err;
110
+ }
111
+ }
112
+ async function initProject() {
113
+ const cwd = process.cwd();
114
+ console.log("\n Browsecraft - Project Setup\n");
115
+ const configPath = path.join(cwd, "browsecraft.config.ts");
116
+ if (!fs.existsSync(configPath)) {
117
+ fs.writeFileSync(configPath, `import { defineConfig } from 'browsecraft';
118
+
119
+ export default defineConfig({
120
+ // Browser to use: 'chrome' | 'firefox' | 'edge'
121
+ browser: 'chrome',
122
+
123
+ // Run tests in headless mode
124
+ headless: true,
125
+
126
+ // Base URL for page.goto() calls
127
+ // baseURL: 'http://localhost:3000',
128
+
129
+ // Global timeout for actions (ms)
130
+ timeout: 30_000,
131
+
132
+ // Take screenshots on failure
133
+ screenshot: 'on-failure',
134
+ });
135
+ `);
136
+ console.log(" Created browsecraft.config.ts");
137
+ } else {
138
+ console.log(" browsecraft.config.ts already exists, skipping");
139
+ }
140
+ const testsDir = path.join(cwd, "tests");
141
+ if (!fs.existsSync(testsDir)) {
142
+ fs.mkdirSync(testsDir, { recursive: true });
143
+ }
144
+ const exampleTest = path.join(testsDir, "example.test.ts");
145
+ if (!fs.existsSync(exampleTest)) {
146
+ fs.writeFileSync(exampleTest, `import { test, expect } from 'browsecraft';
147
+
148
+ test('homepage has correct title', async ({ page }) => {
149
+ await page.goto('https://example.com');
150
+ await expect(page).toHaveTitle('Example Domain');
151
+ });
152
+
153
+ test('can navigate to more info', async ({ page }) => {
154
+ await page.goto('https://example.com');
155
+ await page.click('More information');
156
+ await expect(page).toHaveURL(/iana\\.org/);
157
+ });
158
+ `);
159
+ console.log(" Created tests/example.test.ts");
160
+ } else {
161
+ console.log(" tests/example.test.ts already exists, skipping");
162
+ }
163
+ const gitignorePath = path.join(cwd, ".gitignore");
164
+ if (fs.existsSync(gitignorePath)) {
165
+ const content = await import('fs').then((fs) => fs.readFileSync(gitignorePath, "utf-8"));
166
+ if (!content.includes(".browsecraft")) {
167
+ fs.writeFileSync(gitignorePath, `${content.trimEnd()}
168
+
169
+ # Browsecraft
170
+ .browsecraft/
171
+ `);
172
+ console.log(" Updated .gitignore");
173
+ }
174
+ }
175
+ console.log("\n Setup complete! Run your first test:\n");
176
+ console.log(" npx browsecraft test\n");
177
+ }
178
+ async function loadConfig() {
179
+ const cwd = process.cwd();
180
+ const candidates = [
181
+ "browsecraft.config.ts",
182
+ "browsecraft.config.js",
183
+ "browsecraft.config.mjs",
184
+ "browsecraft.config.mts"
185
+ ];
186
+ for (const name of candidates) {
187
+ const configPath = path.resolve(cwd, name);
188
+ if (fs.existsSync(configPath)) {
189
+ try {
190
+ const mod = await import(configPath);
191
+ return mod.default ?? mod;
192
+ } catch {
193
+ console.warn(`Warning: Could not load ${name}. Using defaults.`);
194
+ }
195
+ }
196
+ }
197
+ return void 0;
198
+ }
199
+ var tsLoaderRegistered = false;
200
+ async function ensureTypeScriptLoader() {
201
+ if (tsLoaderRegistered) return;
202
+ const execArgs = process.execArgv.join(" ");
203
+ if (execArgs.includes("tsx") || execArgs.includes("ts-node") || execArgs.includes("loader") || process.versions.bun) {
204
+ tsLoaderRegistered = true;
205
+ return;
206
+ }
207
+ for (const loader of ["tsx/esm", "ts-node/esm"]) {
208
+ try {
209
+ const { register } = await import('module');
210
+ if (typeof register === "function") {
211
+ register(loader, url.pathToFileURL("./"));
212
+ tsLoaderRegistered = true;
213
+ return;
214
+ }
215
+ } catch {
216
+ }
217
+ }
218
+ console.error(
219
+ "\n Error: Cannot import TypeScript test files.\n Install tsx (recommended) or ts-node:\n\n npm install -D tsx\n\n Or run browsecraft with tsx:\n\n npx tsx node_modules/.bin/browsecraft test\n"
220
+ );
221
+ process.exit(1);
222
+ }
223
+ function parseFlags(args) {
224
+ const flags = {};
225
+ for (let i = 0; i < args.length; i++) {
226
+ const arg = args[i];
227
+ switch (arg) {
228
+ case "--headed":
229
+ flags.headed = true;
230
+ break;
231
+ case "--headless":
232
+ flags.headless = true;
233
+ break;
234
+ case "--debug":
235
+ flags.debug = true;
236
+ break;
237
+ case "--bail":
238
+ flags.bail = true;
239
+ break;
240
+ case "--browser":
241
+ flags.browser = args[++i];
242
+ break;
243
+ case "--workers":
244
+ flags.workers = Number.parseInt(args[++i] ?? "1", 10);
245
+ break;
246
+ case "--timeout":
247
+ flags.timeout = Number.parseInt(args[++i] ?? "30000", 10);
248
+ break;
249
+ case "--retries":
250
+ flags.retries = Number.parseInt(args[++i] ?? "0", 10);
251
+ break;
252
+ case "--grep":
253
+ case "-g":
254
+ flags.grep = args[++i];
255
+ break;
256
+ }
257
+ }
258
+ return flags;
259
+ }
260
+ function printHelp() {
261
+ console.log(`
262
+ browsecraft v${VERSION} -- AI-native browser testing
263
+
264
+ Usage:
265
+ browsecraft test [files...] [options]
266
+ browsecraft init
267
+
268
+ Commands:
269
+ test Run browser tests
270
+ init Create a new project with example config and test
271
+
272
+ Options:
273
+ --browser <name> Browser to use: chrome, firefox, edge (default: chrome)
274
+ --headed Run in headed mode (show the browser)
275
+ --headless Run in headless mode (default)
276
+ --workers <n> Number of parallel workers (default: half CPU cores)
277
+ --timeout <ms> Global timeout in milliseconds (default: 30000)
278
+ --retries <n> Retry failed tests n times (default: 0)
279
+ --grep <pattern> Only run tests matching pattern
280
+ --bail Stop after first failure
281
+ --debug Enable verbose debug logging
282
+ -h, --help Show this help message
283
+ -v, --version Show version
284
+
285
+ Examples:
286
+ browsecraft test # Run all tests
287
+ browsecraft test tests/login.test.ts # Run specific file
288
+ browsecraft test --headed --browser firefox
289
+ browsecraft test --grep "login" --bail
290
+ `);
291
+ }
292
+ main().catch((err) => {
293
+ console.error("Fatal error:", err.message);
294
+ process.exit(1);
295
+ });
296
+ //# sourceMappingURL=cli.cjs.map
297
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +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 { resolve, join } from 'node:path';\nimport { existsSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nimport { TestRunner, type RunnerOptions, type RunnableTest } from 'browsecraft-runner';\nimport { resolveConfig, type UserConfig } from './config.js';\nimport { testRegistry, runTest, runAfterAllHooks, type TestCase } from './test.js';\nimport { Browser } from './browser.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(configPath, `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\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(exampleTest, `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\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' Install tsx (recommended) or ts-node:\\n\\n' +\n\t\t' npm install -D tsx\\n\\n' +\n\t\t' Or run browsecraft with tsx:\\n\\n' +\n\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.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env node
2
+ import { resolveConfig, Browser, testRegistry, runTest } from './chunk-77HRTGXZ.js';
3
+ import { join, resolve } from 'path';
4
+ import { existsSync, writeFileSync, mkdirSync } from 'fs';
5
+ import { pathToFileURL } from 'url';
6
+ import { TestRunner } from 'browsecraft-runner';
7
+
8
+ var VERSION = "0.1.0";
9
+ async function main() {
10
+ const args = process.argv.slice(2);
11
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
12
+ printHelp();
13
+ return;
14
+ }
15
+ if (args.includes("--version") || args.includes("-v")) {
16
+ console.log(`browsecraft v${VERSION}`);
17
+ return;
18
+ }
19
+ const command = args[0];
20
+ switch (command) {
21
+ case "test":
22
+ await runTests(args.slice(1));
23
+ break;
24
+ case "init":
25
+ await initProject();
26
+ break;
27
+ default:
28
+ if (command && (command.endsWith(".ts") || command.endsWith(".js"))) {
29
+ await runTests(args);
30
+ } else {
31
+ console.error(`Unknown command: ${command}`);
32
+ console.error('Run "browsecraft --help" for usage information.');
33
+ process.exit(1);
34
+ }
35
+ }
36
+ }
37
+ async function runTests(args) {
38
+ const flags = parseFlags(args);
39
+ const filePatterns = args.filter((a) => !a.startsWith("--"));
40
+ const userConfig = await loadConfig();
41
+ const config = resolveConfig(userConfig);
42
+ if (flags.headed || flags.headless === false) {
43
+ config.headless = false;
44
+ }
45
+ if (flags.browser) {
46
+ config.browser = flags.browser;
47
+ }
48
+ if (flags.workers !== void 0) {
49
+ config.workers = flags.workers;
50
+ }
51
+ if (flags.timeout !== void 0) {
52
+ config.timeout = flags.timeout;
53
+ }
54
+ if (flags.retries !== void 0) {
55
+ config.retries = flags.retries;
56
+ }
57
+ if (flags.debug) {
58
+ config.debug = true;
59
+ }
60
+ const runnerOptions = {
61
+ config,
62
+ files: filePatterns.length > 0 ? filePatterns : void 0,
63
+ grep: flags.grep,
64
+ bail: flags.bail
65
+ };
66
+ const runner = new TestRunner(runnerOptions);
67
+ const loadFile = async (file) => {
68
+ const startIdx = testRegistry.length;
69
+ if (file.endsWith(".ts") || file.endsWith(".mts")) {
70
+ await ensureTypeScriptLoader();
71
+ }
72
+ const fileUrl = pathToFileURL(file).href;
73
+ await import(fileUrl);
74
+ return testRegistry.slice(startIdx).map((tc) => ({
75
+ title: tc.title,
76
+ suitePath: tc.suitePath,
77
+ skip: tc.skip,
78
+ only: tc.only,
79
+ options: tc.options,
80
+ fn: tc.fn
81
+ }));
82
+ };
83
+ let sharedBrowser;
84
+ try {
85
+ sharedBrowser = await Browser.launch({
86
+ browser: config.browser,
87
+ headless: config.headless,
88
+ executablePath: config.executablePath,
89
+ debug: config.debug,
90
+ timeout: config.timeout
91
+ });
92
+ } catch (err) {
93
+ console.error(`Failed to launch browser: ${err instanceof Error ? err.message : String(err)}`);
94
+ process.exit(1);
95
+ }
96
+ const executeTest = async (test) => {
97
+ return runTest(test, sharedBrowser);
98
+ };
99
+ try {
100
+ const exitCode = await runner.run(loadFile, executeTest);
101
+ await sharedBrowser.close().catch(() => {
102
+ });
103
+ process.exit(exitCode);
104
+ } catch (err) {
105
+ await sharedBrowser?.close().catch(() => {
106
+ });
107
+ throw err;
108
+ }
109
+ }
110
+ async function initProject() {
111
+ const cwd = process.cwd();
112
+ console.log("\n Browsecraft - Project Setup\n");
113
+ const configPath = join(cwd, "browsecraft.config.ts");
114
+ if (!existsSync(configPath)) {
115
+ writeFileSync(configPath, `import { defineConfig } from 'browsecraft';
116
+
117
+ export default defineConfig({
118
+ // Browser to use: 'chrome' | 'firefox' | 'edge'
119
+ browser: 'chrome',
120
+
121
+ // Run tests in headless mode
122
+ headless: true,
123
+
124
+ // Base URL for page.goto() calls
125
+ // baseURL: 'http://localhost:3000',
126
+
127
+ // Global timeout for actions (ms)
128
+ timeout: 30_000,
129
+
130
+ // Take screenshots on failure
131
+ screenshot: 'on-failure',
132
+ });
133
+ `);
134
+ console.log(" Created browsecraft.config.ts");
135
+ } else {
136
+ console.log(" browsecraft.config.ts already exists, skipping");
137
+ }
138
+ const testsDir = join(cwd, "tests");
139
+ if (!existsSync(testsDir)) {
140
+ mkdirSync(testsDir, { recursive: true });
141
+ }
142
+ const exampleTest = join(testsDir, "example.test.ts");
143
+ if (!existsSync(exampleTest)) {
144
+ writeFileSync(exampleTest, `import { test, expect } from 'browsecraft';
145
+
146
+ test('homepage has correct title', async ({ page }) => {
147
+ await page.goto('https://example.com');
148
+ await expect(page).toHaveTitle('Example Domain');
149
+ });
150
+
151
+ test('can navigate to more info', async ({ page }) => {
152
+ await page.goto('https://example.com');
153
+ await page.click('More information');
154
+ await expect(page).toHaveURL(/iana\\.org/);
155
+ });
156
+ `);
157
+ console.log(" Created tests/example.test.ts");
158
+ } else {
159
+ console.log(" tests/example.test.ts already exists, skipping");
160
+ }
161
+ const gitignorePath = join(cwd, ".gitignore");
162
+ if (existsSync(gitignorePath)) {
163
+ const content = await import('fs').then((fs) => fs.readFileSync(gitignorePath, "utf-8"));
164
+ if (!content.includes(".browsecraft")) {
165
+ writeFileSync(gitignorePath, `${content.trimEnd()}
166
+
167
+ # Browsecraft
168
+ .browsecraft/
169
+ `);
170
+ console.log(" Updated .gitignore");
171
+ }
172
+ }
173
+ console.log("\n Setup complete! Run your first test:\n");
174
+ console.log(" npx browsecraft test\n");
175
+ }
176
+ async function loadConfig() {
177
+ const cwd = process.cwd();
178
+ const candidates = [
179
+ "browsecraft.config.ts",
180
+ "browsecraft.config.js",
181
+ "browsecraft.config.mjs",
182
+ "browsecraft.config.mts"
183
+ ];
184
+ for (const name of candidates) {
185
+ const configPath = resolve(cwd, name);
186
+ if (existsSync(configPath)) {
187
+ try {
188
+ const mod = await import(configPath);
189
+ return mod.default ?? mod;
190
+ } catch {
191
+ console.warn(`Warning: Could not load ${name}. Using defaults.`);
192
+ }
193
+ }
194
+ }
195
+ return void 0;
196
+ }
197
+ var tsLoaderRegistered = false;
198
+ async function ensureTypeScriptLoader() {
199
+ if (tsLoaderRegistered) return;
200
+ const execArgs = process.execArgv.join(" ");
201
+ if (execArgs.includes("tsx") || execArgs.includes("ts-node") || execArgs.includes("loader") || process.versions.bun) {
202
+ tsLoaderRegistered = true;
203
+ return;
204
+ }
205
+ for (const loader of ["tsx/esm", "ts-node/esm"]) {
206
+ try {
207
+ const { register } = await import('module');
208
+ if (typeof register === "function") {
209
+ register(loader, pathToFileURL("./"));
210
+ tsLoaderRegistered = true;
211
+ return;
212
+ }
213
+ } catch {
214
+ }
215
+ }
216
+ console.error(
217
+ "\n Error: Cannot import TypeScript test files.\n Install tsx (recommended) or ts-node:\n\n npm install -D tsx\n\n Or run browsecraft with tsx:\n\n npx tsx node_modules/.bin/browsecraft test\n"
218
+ );
219
+ process.exit(1);
220
+ }
221
+ function parseFlags(args) {
222
+ const flags = {};
223
+ for (let i = 0; i < args.length; i++) {
224
+ const arg = args[i];
225
+ switch (arg) {
226
+ case "--headed":
227
+ flags.headed = true;
228
+ break;
229
+ case "--headless":
230
+ flags.headless = true;
231
+ break;
232
+ case "--debug":
233
+ flags.debug = true;
234
+ break;
235
+ case "--bail":
236
+ flags.bail = true;
237
+ break;
238
+ case "--browser":
239
+ flags.browser = args[++i];
240
+ break;
241
+ case "--workers":
242
+ flags.workers = Number.parseInt(args[++i] ?? "1", 10);
243
+ break;
244
+ case "--timeout":
245
+ flags.timeout = Number.parseInt(args[++i] ?? "30000", 10);
246
+ break;
247
+ case "--retries":
248
+ flags.retries = Number.parseInt(args[++i] ?? "0", 10);
249
+ break;
250
+ case "--grep":
251
+ case "-g":
252
+ flags.grep = args[++i];
253
+ break;
254
+ }
255
+ }
256
+ return flags;
257
+ }
258
+ function printHelp() {
259
+ console.log(`
260
+ browsecraft v${VERSION} -- AI-native browser testing
261
+
262
+ Usage:
263
+ browsecraft test [files...] [options]
264
+ browsecraft init
265
+
266
+ Commands:
267
+ test Run browser tests
268
+ init Create a new project with example config and test
269
+
270
+ Options:
271
+ --browser <name> Browser to use: chrome, firefox, edge (default: chrome)
272
+ --headed Run in headed mode (show the browser)
273
+ --headless Run in headless mode (default)
274
+ --workers <n> Number of parallel workers (default: half CPU cores)
275
+ --timeout <ms> Global timeout in milliseconds (default: 30000)
276
+ --retries <n> Retry failed tests n times (default: 0)
277
+ --grep <pattern> Only run tests matching pattern
278
+ --bail Stop after first failure
279
+ --debug Enable verbose debug logging
280
+ -h, --help Show this help message
281
+ -v, --version Show version
282
+
283
+ Examples:
284
+ browsecraft test # Run all tests
285
+ browsecraft test tests/login.test.ts # Run specific file
286
+ browsecraft test --headed --browser firefox
287
+ browsecraft test --grep "login" --bail
288
+ `);
289
+ }
290
+ main().catch((err) => {
291
+ console.error("Fatal error:", err.message);
292
+ process.exit(1);
293
+ });
294
+ //# sourceMappingURL=cli.js.map
295
+ //# sourceMappingURL=cli.js.map