// server.ts - Local server that receives click data from the Chrome extension // Run with: ts-node --transpile-only server.ts import * as http from "http"; import * as fs from "fs"; import * as path from "path"; const PORT = 3000; const OUTPUT_FILE = path.join(__dirname, "recorded_clicks.ts"); const SUGGESTIONS_FILE = path.join(__dirname, "suggestions.txt"); // Dynamically find and load the first .po.ts file in the same folder function loadPageObject(): Record { const files = fs.readdirSync(__dirname).filter(f => f.endsWith(".po.ts")); if (files.length === 0) { console.warn("No .po.ts file found in server directory!"); return {}; } const filePath = path.join(__dirname, files[0]); console.log(`Loading page object from: ${filePath}`); const mod = require(filePath); const exportedClass = Object.values(mod)[0] as any; const selectors: Record = {}; for (const [key, value] of Object.entries(exportedClass)) { if (typeof value === "string") { selectors[key] = value as string; } } console.log(`Loaded ${Object.keys(selectors).length} selectors.`); return selectors; } function findMatches(data: any, selectors: Record): string[] { const results: string[] = []; for (const [name, selector] of Object.entries(selectors)) { if (selector.startsWith(".")) { const className = selector.replace(".", ""); if (data.classes.includes(className)) { results.push(name); } } if (data.ariaLabel && selector === data.ariaLabel) { results.push(name); } } return results; } function appendToOutput(matches: string[]) { const lines = matches.map(name => `mainpage.${name}.click();`); fs.appendFileSync(OUTPUT_FILE, lines.join("\n") + "\n"); } function appendSuggestion(data: any) { const firstClass = data.classes.length > 0 ? data.classes[0] : null; // Generate a name from aria-label or first class const name = data.ariaLabel ? data.ariaLabel.replace(/\s+/g, "").toLowerCase() : firstClass ? firstClass.replace(/[-_.]/g, "").toLowerCase() : "unknown"; const lines: string[] = []; lines.push(`// --- Unmatched click (tag: <${data.tag}>, text: "${data.text}") ---`); lines.push(`// Check if these exist and add to your page object:`); if (firstClass) lines.push(`static ${name}class = ".${firstClass}"`); if (data.id) lines.push(`static ${name}id = "#${data.id}"`); if (data.ariaLabel) lines.push(`static ${name}aria = "[aria-label='${data.ariaLabel}']"`); lines.push(``); lines.push(`// Suggested get function:`); // Selector: aria if available, otherwise class const selectorVar = data.ariaLabel ? `Mainpage.${name}aria` : `Mainpage.${name}class`; // Always include text and tag if available const hasText = data.text && data.text.length > 0; const filterParts: string[] = []; if (data.tag) filterParts.push(`data.tag: "${data.tag}"`); if (hasText) filterParts.push(`text: "${data.text}"`); lines.push(`get ${name}() {`); if (hasText) { lines.push(` return cy.nccontains(${selectorVar}, '${data.text}');`); } else { lines.push(` return cy.get(${selectorVar});`); } lines.push(`}`); lines.push(``); lines.push(`// ------------------------------------------------`); lines.push(``); fs.appendFileSync(SUGGESTIONS_FILE, lines.join("\n") + "\n"); } // Load selectors once at startup const selectors = loadPageObject(); const server = http.createServer((req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } if (req.method === "POST" && req.url === "/record") { let body = ""; req.on("data", (chunk) => (body += chunk)); req.on("end", () => { try { const data = JSON.parse(body); console.log("\n--- Click Recorded ---"); console.log("Tag: ", data.tag); console.log("ID: ", data.id); console.log("Aria-label: ", data.ariaLabel); console.log("Classes: ", data.classes.join(", ")); console.log("Text: ", data.text); const matches = findMatches(data, selectors); if (matches.length > 0) { console.log("Matches:"); matches.forEach(m => console.log(` >> mainpage.${m}.click();`)); appendToOutput(matches); } else { console.log(" >> No match — writing to suggestions.txt"); appendSuggestion(data); } console.log("----------------------"); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok" })); } catch (err: any) { console.error("Failed to parse body:", err.message); res.writeHead(400); res.end("Bad request"); } }); } else { res.writeHead(404); res.end("Not found"); } }); server.listen(PORT, () => { console.log(`Cypress Recorder server running on http://localhost:${PORT}`); });