pre work commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
48
content.js
Normal file
48
content.js
Normal file
@@ -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
|
||||||
231
mainpage.po.ts
Normal file
231
mainpage.po.ts
Normal file
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
13
manifest.json
Normal file
13
manifest.json
Normal file
@@ -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": ["<all_urls>"],
|
||||||
|
"js": ["content.js"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
package-lock.json
generated
Normal file
27
package-lock.json
generated
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package.json
Normal file
6
package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"type": "commonjs",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^25.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
recorded_clicks.ts
Normal file
3
recorded_clicks.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mainpage.navbuttons.click();
|
||||||
|
mainpage.navbuttons.click();
|
||||||
|
mainpage.navbuttons.click();
|
||||||
155
server.ts
Normal file
155
server.ts
Normal file
@@ -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<string, string> {
|
||||||
|
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<string, string> = {};
|
||||||
|
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, string>): 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}`);
|
||||||
|
});
|
||||||
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["ES2017"],
|
||||||
|
"types": ["node"],
|
||||||
|
"esModuleInterop": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user