Skip to content

Commit

Permalink
Bug: fixed fsevents and __driname error from ES module scope (#107)
Browse files Browse the repository at this point in the history
Fixes this issue: #106 
v0.0.5

### Fixed:
- Fixed FS build error
- Fixed __driname ESM error
- Fixed CLI --headless flag to override config file

### Changed
- Improved Config file loading 
- Changed config types to be more strict


⚠️ **Known Issues**
- Using this version with React 18 in Next.js 14+ projects may cause
type conflicts with Server Actions and `useFormStatus`
- If you encounter type errors with form actions or React hooks, ensure
you're using React 19
  • Loading branch information
m2rads authored Dec 10, 2024
1 parent 5384332 commit ea2b989
Show file tree
Hide file tree
Showing 18 changed files with 1,535 additions and 1,715 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,12 @@ You can run shortest in your CI/CD pipeline by running tests in headless mode. M
This guide will help you set up the Shortest web app for local development.

### Prerequisites
- React >=19.0.0 (if using with Next.js 14+ or Server Actions)
- Next.js >=14.0.0 (if using Server Components/Actions)

- Node.js
- pnpm
⚠️ **Known Issues**
- Using this package with React 18 in Next.js 14+ projects may cause type conflicts with Server Actions and `useFormStatus`
- If you encounter type errors with form actions or React hooks, ensure you're using React 19

### Getting Started

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
"@radix-ui/react-toast": "^1.2.1",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^22.5.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"@vercel/postgres": "^0.10.0",
"adm-zip": "^0.5.16",
"ai": "^3.4.0",
Expand Down
13 changes: 13 additions & 0 deletions packages/shortest/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.5] - 2024-12-09

### Fixed
- Fixed FS build error
- Fixed CLI --headless flag to override config file

### Changed
- Improved Config file loading

⚠️ **Known Issues**
- Using this version with React 18 in Next.js 14+ projects may cause type conflicts with Server Actions and `useFormStatus`
- If you encounter type errors with form actions or React hooks, ensure you're using React 19

## [0.0.4] - 2024-12-06

### Added
Expand Down
11 changes: 10 additions & 1 deletion packages/shortest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,13 @@ GITHUB_TOTP_SECRET=your_secret # Only for GitHub auth tests
You can run shortest in your CI/CD pipeline by running tests in headless mode. Make sure to add your Anthropic API key to your CI/CD pipeline secrets.

## Documentation
Visit [GitHub](https://github.com/anti-work/shortest) for detailed docs
Visit [GitHub](https://github.com/anti-work/shortest) for detailed docs

### Prerequisites
- React >=19.0.0 (if using with Next.js 14+ or Server Actions)
- Next.js >=14.0.0 (if using Server Components/Actions)

⚠️ **Known Issues**
- Using this package with React 18 in Next.js 14+ projects may cause type conflicts with Server Actions and `useFormStatus`
- If you encounter type errors with form actions or React hooks, ensure you're using React 19

5 changes: 2 additions & 3 deletions packages/shortest/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@antiwork/shortest",
"version": "0.0.4",
"version": "0.0.5",
"description": "AI-powered natural language end-to-end testing framework",
"type": "module",
"main": "./dist/index.js",
Expand Down Expand Up @@ -46,8 +46,7 @@
"tsx": "^4.7.1",
"typescript": "~5.6.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.24",
"playwright": "1.48.2"
"@types/node": "^20.11.24"
},
"engines": {
"node": ">=18"
Expand Down
4 changes: 4 additions & 0 deletions packages/shortest/src/ai/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export class AIClient {
private debugMode: boolean;

constructor(config: AIConfig, debugMode: boolean = false) {
if (!config.apiKey) {
throw new Error('Anthropic API key is required. Set it in shortest.config.ts or ANTHROPIC_API_KEY env var');
}

this.client = new Anthropic({
apiKey: config.apiKey
});
Expand Down
28 changes: 19 additions & 9 deletions packages/shortest/src/browser/manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import { chromium, Browser, BrowserContext } from 'playwright';
import { getConfig } from '../../index';
import path from 'path';
import { mkdirSync, existsSync, rmSync } from 'fs';
import { ShortestConfig } from '../../types/config';
import { URL } from 'url';

export class BrowserManager {
private browser: Browser | null = null;
private context: BrowserContext | null = null;
private config: ShortestConfig;

async launch(): Promise<BrowserContext> {
const config = getConfig();
const baseUrl = config.baseUrl || 'http://localhost:3000';
constructor(config: ShortestConfig) {
this.config = config;
}

private normalizeUrl(url: string): string {
try {
const parsedUrl = new URL(url);
return parsedUrl.toString();
} catch {
return url;
}
}

async launch(): Promise<BrowserContext> {
this.browser = await chromium.launch({
headless: config.headless
headless: this.config.headless ?? false
});

this.context = await this.browser.newContext({
viewport: { width: 1920, height: 1080 }
});

const page = await this.context.newPage();
await page.goto(baseUrl);
await page.goto(this.normalizeUrl(this.config.baseUrl));
await page.waitForLoadState('networkidle');

return this.context;
Expand Down Expand Up @@ -62,7 +72,7 @@ export class BrowserManager {
}

// Navigate first page to baseUrl
const baseUrl = getConfig().baseUrl || 'http://localhost:3000';
const baseUrl = this.config.baseUrl;
await pages[0].goto(baseUrl);
await pages[0].waitForLoadState('networkidle');

Expand Down
53 changes: 45 additions & 8 deletions packages/shortest/src/cli/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { TestRunner } from '../core/runner';
import { GitHubTool } from '../browser/integrations/github';
import pc from 'picocolors';

process.removeAllListeners('warning');
process.on('warning', (warning) => {
if (warning.name === 'DeprecationWarning' && warning.message.includes('punycode')) {
return;
}
console.warn(warning);
});

const VALID_FLAGS = ['--headless', '--github-code', '--debug-ai', '--help', '-h'];
const VALID_PARAMS = ['--target', '--secret'];

Expand Down Expand Up @@ -67,22 +75,36 @@ async function handleGitHubCode(args: string[]) {
}
}

function isValidArg(arg: string): boolean {
// Check if it's a flag
if (VALID_FLAGS.includes(arg)) {
return true;
}

// Check if it's a parameter with value
const paramName = arg.split('=')[0];
if (VALID_PARAMS.includes(paramName)) {
return true;
}

return false;
}

async function main() {
const args = process.argv.slice(2);

// Check for help flag first
if (args.includes('--help') || args.includes('-h')) {
showHelp();
process.exit(0);
}

// Handle GitHub code generation
if (args.includes('--github-code')) {
await handleGitHubCode(args);
}

// Validate remaining flags
const invalidFlags = args.filter(arg => arg.startsWith('--') && !VALID_FLAGS.includes(arg));
const invalidFlags = args
.filter(arg => arg.startsWith('--'))
.filter(arg => !isValidArg(arg));

if (invalidFlags.length > 0) {
console.error(`Error: Invalid argument(s): ${invalidFlags.join(', ')}`);
Expand All @@ -92,19 +114,34 @@ async function main() {
const headless = args.includes('--headless');
const targetUrl = args.find(arg => arg.startsWith('--target='))?.split('=')[1];
const testPattern = args.find(arg => !arg.startsWith('--'));

const debugAI = args.includes('--debug-ai');

const runner = new TestRunner(process.cwd(), true, headless, targetUrl, debugAI);

try {
const runner = new TestRunner(process.cwd(), true, headless, targetUrl, debugAI);
await runner.initialize();

if (testPattern) {
await runner.runFile(testPattern);
} else {
await runner.runAll();
}
} catch (error) {
console.error('Error: Invalid argument(s)');
if (error instanceof Error) {
if (error.message.includes('Config')) {
console.error(pc.red('\nConfiguration Error:'));
console.error(pc.dim(error.message));
console.error(pc.dim('\nMake sure you have a valid shortest.config.ts with all required fields:'));
console.error(pc.dim(' - headless: boolean'));
console.error(pc.dim(' - baseUrl: string'));
console.error(pc.dim(' - testDir: string | string[]'));
console.error(pc.dim(' - anthropicKey: string'));
console.error();
} else {
console.error(pc.red('\nError:'), error.message);
}
} else {
console.error(pc.red('\nUnknown error occurred'));
}
process.exit(1);
}
}
Expand Down
27 changes: 20 additions & 7 deletions packages/shortest/src/core/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { build, BuildOptions } from 'esbuild';
import { join, resolve } from 'path';
import { mkdirSync, existsSync, writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { defaultConfig } from '../../types';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

export class TestCompiler {
private cacheDir: string;
Expand All @@ -24,8 +25,19 @@ export class TestCompiler {
'url',
'crypto',
'buffer',
'querystring'
]
'querystring',
'fsevents'
],
banner: {
js: `
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
import { createRequire } from "module";
const require = createRequire(import.meta.url);
`
}
};

constructor() {
Expand Down Expand Up @@ -67,7 +79,7 @@ export class TestCompiler {
const absolutePath = resolve(cwd, filePath);

if (!existsSync(absolutePath)) {
return { default: defaultConfig };
throw new Error(`Config file not found: ${filePath}`);
}

try {
Expand All @@ -79,10 +91,11 @@ export class TestCompiler {
});

const code = result.outputFiles[0].text;
return import(`data:text/javascript;base64,${Buffer.from(code).toString('base64')}`);
const tempFile = join(this.cacheDir, 'config.mjs');
writeFileSync(tempFile, code);
return import(`file://${tempFile}`);
} catch (error) {
console.warn(`Error loading config from ${absolutePath}:`, error);
return { default: defaultConfig };
throw new Error(`Failed to load config from ${absolutePath}: ${error}`);
}
}
}
54 changes: 18 additions & 36 deletions packages/shortest/src/core/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class TestRunner {
private forceHeadless: boolean;
private targetUrl: string | undefined;
private compiler: TestCompiler;
private browserManager: BrowserManager;
private browserManager!: BrowserManager;
private logger: Logger;
private debugAI: boolean;

Expand All @@ -40,43 +40,30 @@ export class TestRunner {
this.targetUrl = targetUrl;
this.debugAI = debugAI;
this.compiler = new TestCompiler();
this.browserManager = new BrowserManager();
this.logger = new Logger();
}

async initialize() {
await this.initializeConfig();
}

private async initializeConfig() {
const configFiles = [
'shortest.config.ts',
'shortest.config.js',
'shortest.config.mjs'
];
// Initialize global config first
await initialize();
this.config = getConfig();

for (const file of configFiles) {
try {
const module = await this.compiler.loadModule(file, this.cwd);
if (module.default) {
this.config = module.default;

if (this.forceHeadless) {
this.config.headless = true;
}
// Override with CLI options
if (this.forceHeadless) {
this.config = {
...this.config,
headless: true
};
}

if (this.targetUrl) {
this.config.baseUrl = this.targetUrl;
}

return;
}
} catch (error) {
continue;
}
if (this.targetUrl) {
this.config = {
...this.config,
baseUrl: this.targetUrl
};
}

this.config = getConfig();
this.browserManager = new BrowserManager(this.config);
}

private async findTestFiles(pattern?: string): Promise<string[]> {
Expand Down Expand Up @@ -127,13 +114,8 @@ export class TestRunner {
}
});

const apiKey = this.config.anthropicKey || process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error('Anthropic API key not found');
}

const aiClient = new AIClient({
apiKey,
apiKey: this.config.anthropicKey,
model: 'claude-3-5-sonnet-20241022',
maxMessages: 10,
debug: this.debugAI
Expand Down
Loading

0 comments on commit ea2b989

Please sign in to comment.