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);
|
const beautifiedHtml = this.beautifyHtml(html);
|
||||||
await fs.writeFile(path.join(dumpDir, 'dom.html'), beautifiedHtml);
|
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)
|
// Create screenshot with viewport lock (if enabled)
|
||||||
if (this.enableScreenshots) {
|
if (this.enableScreenshots) {
|
||||||
if (!this.headless) {
|
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) {
|
beautifyHtml(html) {
|
||||||
let beautified = html;
|
let beautified = html;
|
||||||
|
|
||||||
|
|||||||
@@ -120,16 +120,13 @@ class TestLinter {
|
|||||||
this.info = [];
|
this.info = [];
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
|
|
||||||
// Remove multi-line comments first (like the actual parser does)
|
// Parse content into lines with original line numbers preserved
|
||||||
const contentWithoutMultiLineComments = this.removeMultiLineComments(content);
|
const originalLines = content.split('\n');
|
||||||
|
const cleanedLines = this.preprocessLinesWithComments(originalLines);
|
||||||
// Parse content into lines with line numbers
|
|
||||||
const lines = contentWithoutMultiLineComments.split('\n');
|
|
||||||
const cleanedLines = this.preprocessLines(lines);
|
|
||||||
|
|
||||||
// Run all rules
|
// Run all rules
|
||||||
for (const rule of this.rules) {
|
for (const rule of this.rules) {
|
||||||
rule(cleanedLines, lines);
|
rule(cleanedLines, originalLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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) {
|
preprocessLines(lines) {
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ wait element=label childText="Bestimmungen"
|
|||||||
click element=label childText="Bestimmungen"
|
click element=label childText="Bestimmungen"
|
||||||
sleep 1000 "checkbox checked"
|
sleep 1000 "checkbox checked"
|
||||||
wait element=button childText="Bestellung abschließen"
|
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"
|
sleep 3000 "order completion"
|
||||||
|
|
||||||
# Part 3 - Login to the email account
|
# 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