Add simplified DOM dump generation and improve linter comment handling
This commit is contained in:
7
google.test
Normal file
7
google.test
Normal 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"
|
||||
|
||||
134
src/executor.js
134
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 += `</${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;
|
||||
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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
65
step2.test
Normal 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"
|
||||
Reference in New Issue
Block a user