commit fde37912f039df6c56864a83cb8ed65df492ef39 Author: Burrson Date: Fri Feb 20 22:05:03 2026 +0100 pre work commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/content.js b/content.js new file mode 100644 index 0000000..4fb4141 --- /dev/null +++ b/content.js @@ -0,0 +1,48 @@ +// content.js - Listens for clicks and sends element attributes to local server + +const SERVER_URL = "http://localhost:3000/record"; + +document.addEventListener("click", async (event) => { + let el = event.target; + + // Walk up the DOM until we find a button, link, or element with aria-label + while ( + el && + el !== document.body && + el.tagName.toLowerCase() !== "button" && + el.tagName.toLowerCase() !== "a" && + !el.getAttribute("aria-label") + ) { + el = el.parentElement; + console.log("[Cypress Recorder] Walking up from:", el.tagName, el.className); + } + + // Fall back to original target if nothing useful found + if (!el || el === document.body) el = event.target; + + // ... rest stays the same + + const payload = { + classes: Array.from(el.classList), + id: el.id || null, + ariaLabel: el.getAttribute("aria-label") || null, + tag: el.tagName.toLowerCase(), + text: el.innerText?.trim().slice(0, 100) || null, // bonus: visible text + }; + + console.log("[Cypress Recorder] Click captured:", payload); + + try { + const response = await fetch(SERVER_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + console.error("[Cypress Recorder] Server responded with:", response.status); + } + } catch (err) { + console.error("[Cypress Recorder] Could not reach local server:", err.message); + } +}, true); // useCapture: true so we catch clicks on all elements diff --git a/mainpage.po.ts b/mainpage.po.ts new file mode 100644 index 0000000..847f61e --- /dev/null +++ b/mainpage.po.ts @@ -0,0 +1,231 @@ +import { Util } from "../util"; + +export class Mainpage { + static button = ".mdc-button" + static menubutton = ".mat-mdc-menu-item" + static menutextbutton = ".mat-mdc-menu-item-text" + static openmenu = ".mat-mdc-menu-trigger" + static navbuttons = ".mat-mdc-tooltip-trigger" + static inputfield = ".mat-mdc-input-element" + static panelbutton = ".cmdsk-wlcm__panel-action" //cmdsk-wlcm__panel-action + static yuvform = ".yuv-object-form-element" + static autocomplete = ".mat-mdc-autocomplete-panel" + static calender = ".calender" + static checkbox = ".mdc-checkbox" + static formcategory = ".main" + static buttontext = ".mdc-button__label" + static tab = ".mdc-tab" + static casefunction = ".mdc-evolution-chip__action" + static documentmenubutton = ".cdk-menu-item" + static searchfield = ".search" + static loadingspinner = ".mat-mdc-progress-spinner" + static listitem = ".mdc-list-item" + static tile = ".tile" + + static treenode = ".mat-tree-node" + + get Uploadbutton() { + return cy.nccontains(Mainpage.menubutton, 'Upload document'); + } + + get Casecreatebutton() { + return cy.nccontains(Mainpage.menubutton, ' Create case '); + } + + get Openbutton() { + return cy.nccontains(Mainpage.openmenu, "add"); + } + + get Navbuttons() { + return cy.get(Mainpage.navbuttons); + } + + get Homebutton(){ + return cy.nccontains(Mainpage.navbuttons, 'home') + } + + get Casenavbutton(){ + return cy.nccontains(Mainpage.navbuttons, 'cases') + } + + get Dokumentenavbutton(){ + return cy.nccontains(Mainpage.navbuttons, 'description') + } + + get Deletebutton(){ + return cy.nccontains(Mainpage.documentmenubutton, 'delete') + } + + Casedeletionbutton(selector: string){ + return cy.contains(selector).parent().parent().parent().find(Mainpage.navbuttons).eq(0) + } + + + get Kebabbutton(){ + return cy.ncnccontains(Mainpage.openmenu, 'more_vert') + } + + get Deleteworkspacebutton(){ + return cy.nccontains(Mainpage.menubutton, 'Delete workspace') + } + + get Confirmbutton(){ + return cy.nccontains(Mainpage.button, 'Confirm') + } + + get Deleteconfirmbutton(){ + return cy.nccontains(Mainpage.button, 'Delete') + } + + get Inputfield() { + return cy.get(Mainpage.inputfield); + } + + get Panelbutton() { + return cy.get(Mainpage.panelbutton); + } + + get CasePanelbutton() { + return cy.nccontains(Mainpage.panelbutton, 'case') + } + + get Yuvform(){ + return cy.get(Mainpage.yuvform) + } + + Caseform(selector: string){ + return cy.nccontains(Mainpage.yuvform, selector) + } + + get Autocomplete(){ + return cy.get(Mainpage.autocomplete) + } + + Caseformcalender(selector: string){ + return cy.nccontains(Mainpage.yuvform, selector).find(Mainpage.calender) + } + + get Formdateselect(){ + return cy.nccontains('button', "Select") + } + + get Checkbox(){ + return cy.get(Mainpage.checkbox) + } + + + Formcategory(selector: string){ + return cy.nccontains(Mainpage.formcategory, selector) + } + + get Formconfirm(){ + return cy.nccontains('button', /^ Create $/i) + } + + get Historytab(){ + return cy.nccontains(Mainpage.tab, "history") + } + + get Metadatatab(){ + return cy.nccontains(Mainpage.tab, "Metadata") + } + + get Renamebutton(){ + return cy.nccontains(Mainpage.navbuttons, "Rename") + } + + get Renamefield(){ + return cy.get('yuv-string[formcontrolname="name"] input') + } + + get Renameconfirm(){ + return cy.nccontains(Mainpage.button, "Rename") + } + + + CheckAIfunction(wantedfunction: string){ + return cy.nccontains(Mainpage.casefunction, wantedfunction) + } + + CheckAIlogo(){ + return cy.nccontains("robot_2") + } + + get sortingbutton(){ + return cy.nccontains("swap_vert") + } + + + get firsttreenode(){ + return cy.get('button[mattreenodetoggle]').eq(0) + } + + get Metadatainput(){ + return cy.get('[data-name="appCasem:description"]').eq(1) + } + + get Metadatainputsearch(){ + return cy.get('[data-name="appCasem:description"]').eq(3) + } + + get Metadataconfirm(){ + return cy.nccontains('button', /^ Save $/i) + } + + Suchedokument(name: string){ + cy.get(Mainpage.searchfield).type(name) + cy.get('[aria-label="Start search"]').click( {force: true} ) + } + + Waitforloading(){ + cy.get(Mainpage.loadingspinner).should("not.exist"); + } + + get firstaction(){ + return cy.get('[title="Actions for Processphase: Registration"]') + } + + get translatedocument(){ + return cy.get('[aria-label="Execute Action: Translate document"]') + } + + get startaction(){ + return cy.nccontains(Mainpage.menutextbutton, "Create action") + } + + get adddokument(){ + return cy.nccontains(" Add document to action ") + } + + get adddokumenttocase(){ + return cy.nccontains(" Add to case ") + } + + get confirmtranslate(){ + return cy.nccontains('button', "Translate") + } + + Checkfirststarted(){ + cy.get('[class="node exists"]').get('[title="Actions for Processphase: Registration"]') + } + + get fileinput(){ + return cy.get('input[type="file"]') + } + + get attachdokumentclass(){ + return cy.get('[id="mat-select-0"]') + } + + get listitem(){ + return cy.get(Mainpage.listitem) + } + + get tile(){ + return cy.get(Mainpage.tile) + } + + get notification(){ + return cy.nccontains("notifications") + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d5307ed --- /dev/null +++ b/manifest.json @@ -0,0 +1,13 @@ +{ + "manifest_version": 3, + "name": "Cypress Click Recorder", + "version": "1.0", + "description": "Records clicks and sends element attributes to local server", + "permissions": ["activeTab", "scripting"], + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..77d3c27 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "files", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/node": "^25.3.0" + } + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "dev": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..25b18c9 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "type": "commonjs", + "devDependencies": { + "@types/node": "^25.3.0" + } +} diff --git a/recorded_clicks.ts b/recorded_clicks.ts new file mode 100644 index 0000000..99e2796 --- /dev/null +++ b/recorded_clicks.ts @@ -0,0 +1,3 @@ +mainpage.navbuttons.click(); +mainpage.navbuttons.click(); +mainpage.navbuttons.click(); diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..6809e01 --- /dev/null +++ b/server.ts @@ -0,0 +1,155 @@ +// 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 (use whichever selector fits best):`); + + // Prefer aria > id > class for the get function selector variable + const selectorVar = data.ariaLabel + ? `Mainpage.${name}aria` + : data.id + ? `Mainpage.${name}id` + : `Mainpage.${name}class`; + + lines.push(`get ${name}() {`); + 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}`); +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dcae78d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2017", + "lib": ["ES2017"], + "types": ["node"], + "esModuleInterop": true + } +}