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 += `${element.tag}>`; + return html; + } + + // Generate HTML header and structure + let html = '\n\n
\n\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