From f44b66b15274593ed994010c718124f65ee484eb Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 17 Jul 2025 06:00:56 +0200 Subject: [PATCH] Add simplified DOM dump generation and improve linter comment handling --- google.test | 7 +++ src/executor.js | 134 ++++++++++++++++++++++++++++++++++++++++++++++++ src/linter.js | 71 ++++++++++++++++++++++--- step1.test | 2 +- step2.test | 65 +++++++++++++++++++++++ 5 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 google.test create mode 100644 step2.test diff --git a/google.test b/google.test new file mode 100644 index 0000000..ba344f1 --- /dev/null +++ b/google.test @@ -0,0 +1,7 @@ +use "Chrome" + +# Part 1 - Load Google and wait for cookie consent +open "https://google.de" +sleep 2000 "load" +dump "load" + diff --git a/src/executor.js b/src/executor.js index eae8fc1..7c90405 100644 --- a/src/executor.js +++ b/src/executor.js @@ -856,6 +856,10 @@ class TestExecutor { const beautifiedHtml = this.beautifyHtml(html); await fs.writeFile(path.join(dumpDir, 'dom.html'), beautifiedHtml); + // Create simplified DOM dump for test development + const simplifiedDom = await this.createSimplifiedDom(); + await fs.writeFile(path.join(dumpDir, 'simplified-dom.html'), simplifiedDom); + // Create screenshot with viewport lock (if enabled) if (this.enableScreenshots) { if (!this.headless) { @@ -891,6 +895,136 @@ class TestExecutor { } } + async createSimplifiedDom() { + // Extract simplified DOM structure with only essential elements and attributes + const simplifiedStructure = await this.page.evaluate(() => { + // Elements we want to keep + const keepElements = ['button', 'input', 'textarea', 'span', 'a']; + + // Attributes we want to keep + const keepAttributes = ['id', 'class', 'name', 'value', 'type']; + + function processElement(element) { + const tagName = element.tagName.toLowerCase(); + + // Only process elements we want to keep + if (!keepElements.includes(tagName)) { + return null; + } + + // Create simplified element representation + const simplified = { + tag: tagName, + attributes: {}, + text: '', + children: [] + }; + + // Extract relevant attributes + keepAttributes.forEach(attr => { + if (element.hasAttribute(attr)) { + const value = element.getAttribute(attr); + if (value && value.trim()) { + simplified.attributes[attr] = value.trim(); + } + } + }); + + // Get all text content from this element and its descendants + // This captures text even when it's nested in elements we don't keep + const allTextContent = element.textContent.trim(); + + if (allTextContent) { + simplified.text = allTextContent; + } + + // Process children recursively + Array.from(element.children).forEach(child => { + const processedChild = processElement(child); + if (processedChild) { + simplified.children.push(processedChild); + } + }); + + return simplified; + } + + function processContainer(container) { + const results = []; + Array.from(container.children).forEach(child => { + const processed = processElement(child); + if (processed) { + results.push(processed); + } else { + // If the element itself isn't kept, still check its children + results.push(...processContainer(child)); + } + }); + return results; + } + + // Start from body and extract all relevant elements + return processContainer(document.body); + }); + + // Convert to readable HTML format + function renderElement(element, indent = 0) { + const spaces = ' '.repeat(indent); + let html = `${spaces}<${element.tag}`; + + // Add attributes + Object.entries(element.attributes).forEach(([key, value]) => { + html += ` ${key}="${value}"`; + }); + + if (element.children.length === 0 && !element.text) { + html += ' />'; + return html; + } + + html += '>'; + + // Add text content + if (element.text) { + html += element.text; + } + + // Add children + if (element.children.length > 0) { + if (element.text) html += '\n'; + element.children.forEach(child => { + if (element.text) html += '\n'; + html += renderElement(child, indent + 1); + }); + if (element.text) html += '\n' + spaces; + } + + html += ``; + return html; + } + + // Generate HTML header and structure + let html = '\n\n\n Simplified DOM - Test Development Helper\n \n\n\n'; + html += '
\n';
+    
+    // Add the simplified structure
+    if (simplifiedStructure.length === 0) {
+      html += '\n';
+    } else {
+      simplifiedStructure.forEach(element => {
+        html += renderElement(element) + '\n';
+      });
+    }
+    
+    html += '  
\n\n'; + + return html; + } + beautifyHtml(html) { let beautified = html; diff --git a/src/linter.js b/src/linter.js index 64f9676..51e9240 100644 --- a/src/linter.js +++ b/src/linter.js @@ -120,16 +120,13 @@ class TestLinter { this.info = []; this.filename = filename; - // Remove multi-line comments first (like the actual parser does) - const contentWithoutMultiLineComments = this.removeMultiLineComments(content); - - // Parse content into lines with line numbers - const lines = contentWithoutMultiLineComments.split('\n'); - const cleanedLines = this.preprocessLines(lines); + // Parse content into lines with original line numbers preserved + const originalLines = content.split('\n'); + const cleanedLines = this.preprocessLinesWithComments(originalLines); // Run all rules for (const rule of this.rules) { - rule(cleanedLines, lines); + rule(cleanedLines, originalLines); } return { @@ -141,6 +138,66 @@ class TestLinter { }; } + preprocessLinesWithComments(lines) { + const result = []; + let inMultiLineComment = false; + let commentDepth = 0; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + let cleanLine = line; + + // Handle multi-line comments line by line + let lineInComment = inMultiLineComment; + + // Check for multi-line comment start/end on this line + let tempLine = cleanLine; + let tempInComment = inMultiLineComment; + let tempDepth = commentDepth; + + // Process the line character by character to handle multi-line comments + let processedLine = ''; + for (let j = 0; j < tempLine.length; j++) { + const char = tempLine[j]; + const nextChar = j < tempLine.length - 1 ? tempLine[j + 1] : ''; + + if (!tempInComment && char === '/' && nextChar === '*') { + tempInComment = true; + tempDepth = 1; + j++; // Skip the '*' + } else if (tempInComment && char === '/' && nextChar === '*') { + tempDepth++; + j++; // Skip the '*' + } else if (tempInComment && char === '*' && nextChar === '/') { + tempDepth--; + if (tempDepth === 0) { + tempInComment = false; + } + j++; // Skip the '/' + } else if (!tempInComment) { + processedLine += char; + } + } + + // Update state for next line + inMultiLineComment = tempInComment; + commentDepth = tempDepth; + + cleanLine = processedLine; + + // Remove single line comments (but not inside quotes) + cleanLine = this.removeLineComments(cleanLine); + + result.push({ + original: line, + cleaned: cleanLine.trim(), + lineNumber: i + 1 + }); + } + + return result.filter(line => line.cleaned.length > 0); + } + preprocessLines(lines) { const result = []; diff --git a/step1.test b/step1.test index d026fe3..c946fad 100644 --- a/step1.test +++ b/step1.test @@ -61,7 +61,7 @@ wait element=label childText="Bestimmungen" click element=label childText="Bestimmungen" sleep 1000 "checkbox checked" wait element=button childText="Bestellung abschließen" -click element=button childText="Bestellung abschließen" +#click element=button childText="Bestellung abschließen" sleep 3000 "order completion" # Part 3 - Login to the email account diff --git a/step2.test b/step2.test new file mode 100644 index 0000000..d4ae75a --- /dev/null +++ b/step2.test @@ -0,0 +1,65 @@ +/* +PlayWrong Test - Complete E-commerce Flow +This test demonstrates the full purchase flow from product selection to order completion +with beautiful animations and environment variable support +Part 1: Load Growheads, put one item in the cart and go to checkout +Part 2: Fill in the checkout form +Part 3: Login to the email account +*/ +use "Chrome" + +# Part 1 - Load Growheads, put one item in the cart and go to checkout +open "https://growheads.de" +sleep 2000 "page load" +wait element=a href=/Kategorie/Seeds +click element=a href=/Kategorie/Seeds +sleep 2000 "seed click" +wait element=button childText="In den Korb" +click element=button childText="In den Korb" +sleep 2000 "in korb click" +wait element=span class="MuiBadge-badge" childText="1" +click element=button child=span(class="MuiBadge-badge" childText="1") +sleep 2000 "korb click" +wait element=button childText="Weiter zur Kasse" +click element=button childText="Weiter zur Kasse" +sleep 2000 "weiter click" +wait element=input type="email" +fill element=input type="email" value="autotest@growheads.de" +sleep 2000 "email fill" +wait element=input type="password" +fill element=input type="password" value="$PASSWORD" +sleep 2000 "password fill" +wait element=button childText="ANMELDEN" class="MuiButton-fullWidth" +click element=button childText="ANMELDEN" class="MuiButton-fullWidth" +sleep 3000 "anmelden click" + +# Part 2 - Fill in the checkout form +scroll element=span childText="Vorname" +wait element=input name="firstName" +fill element=input name="firstName" value="Max" +sleep 1000 "vorname fill" +wait element=input name="lastName" +fill element=input name="lastName" value="Muster" +sleep 1000 "nachname fill" +wait element=input name="street" +fill element=input name="street" value="Muster" +sleep 1000 "strasse fill" +wait element=input name="houseNumber" +fill element=input name="houseNumber" value="420" +sleep 1000 "hausnummer fill" +wait element=input name="postalCode" +fill element=input name="postalCode" value="42023" +sleep 1000 "plz fill" +wait element=input name="city" +fill element=input name="city" value="Muster" +sleep 1000 "stadt fill" +wait element=textarea name="note" +fill element=textarea name="note" value="Musteranmerkung" +sleep 1000 "note fill" +scroll element=button childText="Bestellung abschließen" +wait element=label childText="Bestimmungen" +click element=label childText="Bestimmungen" +sleep 1000 "checkbox checked" +wait element=button childText="Bestellung abschließen" +sleep 1000 "order completion" +dump "order_completed" \ No newline at end of file