UNPKG

@craftapit/tester

Version:

A focused, LLM-powered testing framework for natural language test scenarios

208 lines (175 loc) 6.19 kB
import { BaseAdapter } from './BaseAdapter'; import { ScreenState, UIAction } from '../types/actions'; import { LLMAdapter } from './LLMAdapter'; export class CraftacoderAdapter extends BaseAdapter implements LLMAdapter { private apiKey: string; private baseUrl: string; private model: string; private provider: string; constructor(config: { apiKey?: string; baseUrl?: string; model?: string; provider?: string; }) { super(config); this.apiKey = config.apiKey || process.env.CRAFTACODER_API_KEY || ''; this.baseUrl = config.baseUrl || 'http://localhost:3000'; this.model = config.model || 'claude-3-sonnet-20240229'; this.provider = config.provider || 'anthropic'; } async initialize(): Promise<void> { console.log('Initializing Craftacoder adapter'); if (!this.apiKey) { throw new Error('Craftacoder API key is required'); } } async cleanup(): Promise<void> { console.log('Cleaning up Craftacoder adapter'); // Nothing to clean up } /** * Complete a prompt with the LLM via Craftacoder API * @param prompt The prompt to complete * @returns The completion text */ async complete(prompt: string): Promise<string> { console.log(`Completing prompt via Craftacoder: ${prompt.substring(0, 50)}...`); try { // Build the endpoint URL for the appropriate provider const endpoint = `/providers/${this.provider}/messages`; // Prepare the request payload according to provider expectations let payload: any; if (this.provider === 'anthropic') { payload = { model: this.model, messages: [{ role: 'user', content: prompt }], max_tokens: 4000, temperature: 0.2 }; } else if (this.provider === 'openai') { payload = { model: this.model, messages: [{ role: 'user', content: prompt }], max_tokens: 4000, temperature: 0.2 }; } else { throw new Error(`Unsupported provider: ${this.provider}`); } const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error(`Craftacoder API error: ${response.status} ${response.statusText}`); } const data = await response.json(); // Extract content based on provider response format let content = ''; if (this.provider === 'anthropic') { content = data.content[0].text; } else if (this.provider === 'openai') { content = data.choices[0].message.content; } return content; } catch (error) { console.error('Error calling Craftacoder API:', error); throw new Error(`Failed to get completion from Craftacoder: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Suggest an action based on the instruction and screen state */ async suggestAction( instruction: string, screenState: ScreenState ): Promise<UIAction> { console.log(`Suggesting action for: ${instruction}`); const prompt = ` You are an AI assistant helping to automate UI testing. Given the following instruction from a test scenario: "${instruction}" And the current screen state: ${JSON.stringify(screenState, null, 2)} Determine the most appropriate action to take. Return a JSON object with: - actionType: "click", "input", "navigate", "wait", "assert", etc. - target: Element to interact with (if applicable) - value: Value to input (if applicable) - reasoning: Why this action was chosen - confidence: A number between 0 and 1 indicating your confidence Response (JSON only): `; try { const content = await this.complete(prompt); // Extract JSON from the response const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/) || content.match(/{[\s\S]*}/); if (jsonMatch) { const actionJson = JSON.parse(jsonMatch[1] || jsonMatch[0]); return { actionType: actionJson.actionType, target: actionJson.target, value: actionJson.value, reasoning: actionJson.reasoning, confidence: actionJson.confidence }; } throw new Error('Could not parse action from LLM response'); } catch (error) { console.error('Error getting action suggestion:', error); // Fallback action return { actionType: 'click', target: { text: 'Submit' }, reasoning: 'Fallback action due to API error', confidence: 0.5 }; } } /** * Verify if a condition is met based on the screen state */ async verifyCondition( condition: string, screenState: ScreenState ): Promise<{ success: boolean; reason?: string }> { console.log(`Verifying condition: ${condition}`); const prompt = ` You are an AI assistant helping to automate UI testing. Given the following condition to verify: "${condition}" And the current screen state: ${JSON.stringify(screenState, null, 2)} Determine if the condition is met. Return a JSON object with: - success: true or false - reason: Explanation of why the condition is met or not met Response (JSON only): `; try { const content = await this.complete(prompt); // Extract JSON from the response const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/) || content.match(/{[\s\S]*}/); if (jsonMatch) { const resultJson = JSON.parse(jsonMatch[1] || jsonMatch[0]); return { success: resultJson.success, reason: resultJson.reason }; } throw new Error('Could not parse verification result from LLM response'); } catch (error) { console.error('Error verifying condition:', error); // Fallback verification return { success: true, reason: 'Fallback verification due to API error' }; } } }