import request from "supertest"; import dotenv from "dotenv"; import { numTokensFromString } from "../utils/tokens"; import OpenAI from "openai"; import { WebsiteScrapeError } from "../utils/types"; import { logErrors } from "../utils/log"; import websitesData from "../data/scrape.json"; import "dotenv/config"; import fs from 'fs'; dotenv.config(); interface WebsiteData { website: string; prompt: string; expected_output: string; } const TEST_URL = "http://127.0.0.1:3002"; describe("Scraping Checkup (E2E)", () => { beforeAll(() => { if (!process.env.TEST_API_KEY) { throw new Error("TEST_API_KEY is not set"); } if (!process.env.OPENAI_API_KEY) { throw new Error("OPENAI_API_KEY is not set"); } }); describe("Scraping website tests with a dataset", () => { it("Should scrape the website and prompt it against OpenAI", async () => { let totalTimeTaken = 0; let passedTests = 0; const batchSize = 15; // Adjusted to comply with the rate limit of 15 per minute const batchPromises = []; let totalTokens = 0; const startTime = new Date().getTime(); const date = new Date(); const logsDir = `logs/${date.getMonth() + 1}-${date.getDate()}-${date.getFullYear()}`; let errorLogFileName = `${logsDir}/run.log_${new Date().toTimeString().split(' ')[0]}`; const errorLog: WebsiteScrapeError[] = []; for (let i = 0; i < websitesData.length; i += batchSize) { // Introducing delay to respect the rate limit of 15 requests per minute await new Promise(resolve => setTimeout(resolve, 10000)); const batch = websitesData.slice(i, i + batchSize); const batchPromise = Promise.all( batch.map(async (websiteData: WebsiteData) => { try { const startTime = new Date().getTime(); const scrapedContent = await request(TEST_URL || "") .post("/v1/scrape") .set("Content-Type", "application/json") .set("Authorization", `Bearer ${process.env.TEST_API_KEY}`) .send({ url: websiteData.website }); const endTime = new Date().getTime(); const timeTaken = endTime - startTime; totalTimeTaken += timeTaken; if (scrapedContent.statusCode !== 200) { console.error(`Failed to scrape ${websiteData.website} ${scrapedContent.statusCode}`); errorLog.push({ website: websiteData.website, prompt: websiteData.prompt, expected_output: websiteData.expected_output, actual_output: "", error: `Failed to scrape website. ${scrapedContent.statusCode} ${scrapedContent.body.error}` }); return null; } const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); const prompt = `Based on this markdown extracted from a website html page, ${websiteData.prompt} Just say 'yes' or 'no' to the question.\nWebsite markdown: ${scrapedContent.body.data.markdown}\n`; let msg = null; const maxRetries = 3; let attempts = 0; while (!msg && attempts < maxRetries) { try { msg = await openai.chat.completions.create({ model: "gpt-4-turbo", max_tokens: 100, temperature: 0, messages: [ { role: "user", content: prompt }, ], }); } catch (error) { console.error(`Attempt ${attempts + 1}: Failed to prompt for ${websiteData.website}, error: ${error}`); attempts++; if (attempts < maxRetries) { console.log(`Retrying... Attempt ${attempts + 1}`); await new Promise(resolve => setTimeout(resolve, 2000)); // Wait for 2 seconds before retrying } } } if (!msg) { console.error(`Failed to prompt for ${websiteData.website} after ${maxRetries} attempts`); errorLog.push({ website: websiteData.website, prompt: websiteData.prompt, expected_output: websiteData.expected_output, actual_output: "", error: "Failed to prompt... model error." }); return null; } const actualOutput = (msg.choices[0].message.content ?? "").toLowerCase() const expectedOutput = websiteData.expected_output.toLowerCase(); const numTokens = numTokensFromString(prompt,"gpt-4") + numTokensFromString(actualOutput,"gpt-4"); totalTokens += numTokens; if (actualOutput.includes(expectedOutput)) { passedTests++; } else { console.error( `This website failed the test: ${websiteData.website}` ); console.error(`Actual output: ${actualOutput}`); errorLog.push({ website: websiteData.website, prompt: websiteData.prompt, expected_output: websiteData.expected_output, actual_output: actualOutput, error: "Output mismatch" }); } return { website: websiteData.website, prompt: websiteData.prompt, expectedOutput, actualOutput, }; } catch (error) { console.error( `Error processing ${websiteData.website}: ${error}` ); errorLog.push({ website: websiteData.website, prompt: websiteData.prompt, expected_output: websiteData.expected_output, actual_output: "", error: `Error processing ${websiteData.website}: ${error}` }); return null; } }) ); batchPromises.push(batchPromise); } (await Promise.all(batchPromises)).flat(); const score = (passedTests / websitesData.length) * 100; const endTime = new Date().getTime(); const timeTaken = (endTime - startTime) / 1000; console.log(`Score: ${score}%`); console.log(`Total tokens: ${totalTokens}`); console.log(`Total time taken: ${totalTimeTaken} miliseconds`); await logErrors(errorLog, timeTaken, totalTokens, score, websitesData.length); if (process.env.ENV === "local" && errorLog.length > 0) { if (!fs.existsSync(logsDir)){ fs.mkdirSync(logsDir, { recursive: true }); } fs.writeFileSync(errorLogFileName, JSON.stringify(errorLog, null, 2)); } expect(score).toBeGreaterThanOrEqual(70); }, 350000); // 150 seconds timeout }); });