Add simplified DOM dump generation and improve linter comment handling

This commit is contained in:
seb
2025-07-17 06:00:56 +02:00
parent a492223e45
commit f44b66b152
5 changed files with 271 additions and 8 deletions

7
google.test Normal file
View File

@@ -0,0 +1,7 @@
use "Chrome"
# Part 1 - Load Google and wait for cookie consent
open "https://google.de"
sleep 2000 "load"
dump "load"

View File

@@ -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 += `</${element.tag}>`;
return html;
}
// Generate HTML header and structure
let html = '<!DOCTYPE html>\n<html>\n<head>\n <title>Simplified DOM - Test Development Helper</title>\n <style>\n';
html += ' body { font-family: Arial, sans-serif; margin: 20px; }\n';
html += ' .info { background: #f0f0f0; padding: 10px; border-radius: 5px; margin-bottom: 20px; }\n';
html += ' pre { background: #f8f8f8; padding: 15px; border-radius: 5px; overflow-x: auto; }\n';
html += ' </style>\n</head>\n<body>\n';
html += ' <pre>\n';
// Add the simplified structure
if (simplifiedStructure.length === 0) {
html += '<!-- No interactive elements found -->\n';
} else {
simplifiedStructure.forEach(element => {
html += renderElement(element) + '\n';
});
}
html += ' </pre>\n</body>\n</html>';
return html;
}
beautifyHtml(html) {
let beautified = html;

View File

@@ -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 = [];

View File

@@ -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

65
step2.test Normal file
View File

@@ -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"