Implement graceful shutdown handling in CLI; add cleanup logic to TestRunner and Executor for improved resource management during termination. Update sleep durations in step1.test for consistency.
This commit is contained in:
62
src/cli.js
62
src/cli.js
@@ -79,8 +79,47 @@ class TestRunner {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
if (this.executor) {
|
||||
await this.executor.cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global reference to runner for cleanup
|
||||
let currentRunner = null;
|
||||
|
||||
// Signal handler for clean shutdown
|
||||
async function handleShutdown(signal) {
|
||||
console.log(`\n🛑 Received ${signal}, shutting down gracefully...`);
|
||||
|
||||
if (currentRunner) {
|
||||
// Signal the executor to stop
|
||||
if (currentRunner.executor) {
|
||||
currentRunner.executor.shouldStop = true;
|
||||
}
|
||||
|
||||
try {
|
||||
await currentRunner.cleanup();
|
||||
console.log('✅ Browser cleanup completed');
|
||||
} catch (error) {
|
||||
console.log('⚠️ Browser cleanup had issues:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('👋 Goodbye!');
|
||||
|
||||
// Give a moment for any remaining cleanup, then exit
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Register signal handlers
|
||||
process.on('SIGINT', handleShutdown); // Ctrl+C
|
||||
process.on('SIGTERM', handleShutdown); // Termination signal
|
||||
|
||||
// CLI handling
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
@@ -118,13 +157,32 @@ async function main() {
|
||||
}
|
||||
|
||||
const runner = new TestRunner({ headless, disableAnimations, fullPageScreenshots, lint, strict });
|
||||
currentRunner = runner; // Store reference for cleanup
|
||||
|
||||
await runner.runTestFile(testFile, profile);
|
||||
try {
|
||||
await runner.runTestFile(testFile, profile);
|
||||
} catch (error) {
|
||||
console.error('❌ Test execution failed:', error.message);
|
||||
|
||||
// Clean up on error
|
||||
try {
|
||||
await runner.cleanup();
|
||||
} catch (cleanupError) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
} finally {
|
||||
currentRunner = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
main().catch((error) => {
|
||||
console.error('❌ Unexpected error:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = TestRunner;
|
||||
114
src/executor.js
114
src/executor.js
@@ -16,6 +16,7 @@ class TestExecutor {
|
||||
this.fullPageScreenshots = options.fullPageScreenshots || false; // Default to viewport screenshots
|
||||
this.enableScreenshots = options.enableScreenshots !== false; // Default to enable screenshots
|
||||
this.variables = {}; // Store extracted variables
|
||||
this.shouldStop = false; // Flag to stop execution on shutdown
|
||||
|
||||
this.profiles = {
|
||||
Chrome: {
|
||||
@@ -44,6 +45,12 @@ class TestExecutor {
|
||||
}
|
||||
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
// Check if we should stop execution (e.g., due to Ctrl+C)
|
||||
if (this.shouldStop) {
|
||||
console.log('🛑 Stopping test execution due to shutdown request');
|
||||
break;
|
||||
}
|
||||
|
||||
const command = commands[i];
|
||||
|
||||
// Check for pause before executing command (only for interactive commands)
|
||||
@@ -58,7 +65,16 @@ class TestExecutor {
|
||||
|
||||
// Update status before executing command
|
||||
if (!this.headless) {
|
||||
await this.updateStatus(command, false);
|
||||
// Find next interactive command for display when paused
|
||||
let nextInteractiveCommand = null;
|
||||
for (let j = i; j < commands.length; j++) {
|
||||
if (this.isInteractiveCommand(commands[j])) {
|
||||
nextInteractiveCommand = commands[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateStatus(command, false, nextInteractiveCommand);
|
||||
}
|
||||
|
||||
// Check for manual dump requests
|
||||
@@ -66,6 +82,12 @@ class TestExecutor {
|
||||
await this.checkDumpRequests();
|
||||
}
|
||||
|
||||
// Check again if we should stop before executing the command
|
||||
if (this.shouldStop) {
|
||||
console.log('🛑 Stopping test execution due to shutdown request');
|
||||
break;
|
||||
}
|
||||
|
||||
const jumpCount = await this.executeCommand(command);
|
||||
|
||||
// Handle jump commands
|
||||
@@ -76,7 +98,16 @@ class TestExecutor {
|
||||
|
||||
// Update status after completing command
|
||||
if (!this.headless) {
|
||||
await this.updateStatus(command, true);
|
||||
// Find next interactive command for display when paused
|
||||
let nextInteractiveCommand = null;
|
||||
for (let j = i + 1; j < commands.length; j++) {
|
||||
if (this.isInteractiveCommand(commands[j])) {
|
||||
nextInteractiveCommand = commands[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateStatus(command, true, nextInteractiveCommand);
|
||||
|
||||
// If this was a step request, pause again after this interactive command
|
||||
if (this.shouldPauseAfterStep && this.isInteractiveCommand(command)) {
|
||||
@@ -204,6 +235,12 @@ class TestExecutor {
|
||||
}
|
||||
|
||||
async executeCommand(command) {
|
||||
// Check if we should stop before executing any command
|
||||
if (this.shouldStop) {
|
||||
console.log('🛑 Skipping command execution due to shutdown request');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a clean one-line representation of the command
|
||||
const commandStr = this.formatCommandForOutput(command);
|
||||
console.log(`Executing: ${commandStr}`);
|
||||
@@ -1003,6 +1040,7 @@ class TestExecutor {
|
||||
const button = this;
|
||||
const stepBtn = document.getElementById('step-btn');
|
||||
const indicator = document.getElementById('status-indicator');
|
||||
const titleEl = document.querySelector('.status-section .status-title');
|
||||
|
||||
if (isPaused) {
|
||||
button.textContent = '▶️ Resume';
|
||||
@@ -1010,6 +1048,11 @@ class TestExecutor {
|
||||
indicator.className = 'status-indicator status-paused';
|
||||
stepBtn.disabled = false;
|
||||
|
||||
// Update title to show next command
|
||||
if (titleEl) {
|
||||
titleEl.innerHTML = '<span id="status-indicator" class="status-indicator status-paused"></span>Next Command (+1 Step)';
|
||||
}
|
||||
|
||||
// Store pause state in window to communicate with executor
|
||||
window.playwrongPaused = true;
|
||||
} else {
|
||||
@@ -1018,6 +1061,11 @@ class TestExecutor {
|
||||
indicator.className = 'status-indicator status-running';
|
||||
stepBtn.disabled = true;
|
||||
|
||||
// Update title to show current command
|
||||
if (titleEl) {
|
||||
titleEl.innerHTML = '<span id="status-indicator" class="status-indicator status-running"></span>Current Command';
|
||||
}
|
||||
|
||||
// Resume execution
|
||||
window.playwrongPaused = false;
|
||||
}
|
||||
@@ -1106,6 +1154,12 @@ class TestExecutor {
|
||||
|
||||
// Wait until user resumes, but also check for dump requests and step requests while paused
|
||||
while (true) {
|
||||
// Check if we should stop execution (e.g., due to Ctrl+C)
|
||||
if (this.shouldStop) {
|
||||
console.log('🛑 Breaking out of pause due to shutdown request');
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.page.waitForTimeout(500); // Check every 500ms
|
||||
|
||||
// Check for dump requests even while paused
|
||||
@@ -1201,7 +1255,7 @@ class TestExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
async updateStatus(command, isCompleted = false) {
|
||||
async updateStatus(command, isCompleted = false, nextInteractiveCommand = null) {
|
||||
if (!this.statusPage || this.headless) return;
|
||||
|
||||
try {
|
||||
@@ -1220,10 +1274,27 @@ class TestExecutor {
|
||||
|
||||
const progress = this.totalCommands > 0 ? (this.completedCommands / this.totalCommands) * 100 : 0;
|
||||
|
||||
await this.statusPage.evaluate(({ command, completed, total, elapsed, progress }) => {
|
||||
// Update current command
|
||||
await this.statusPage.evaluate(({ command, nextInteractiveCommand, completed, total, elapsed, progress }) => {
|
||||
// Update command display based on pause state
|
||||
const commandEl = document.getElementById('current-command');
|
||||
if (commandEl) commandEl.textContent = command;
|
||||
const indicator = document.getElementById('status-indicator');
|
||||
const titleEl = document.querySelector('.status-section .status-title');
|
||||
|
||||
if (window.playwrongPaused && nextInteractiveCommand) {
|
||||
// When paused, show next interactive command
|
||||
if (commandEl) commandEl.textContent = nextInteractiveCommand;
|
||||
if (indicator) indicator.className = 'status-indicator status-paused';
|
||||
if (titleEl) {
|
||||
titleEl.innerHTML = '<span id="status-indicator" class="status-indicator status-paused"></span>Next Command (+1 Step)';
|
||||
}
|
||||
} else {
|
||||
// When running, show current command
|
||||
if (commandEl) commandEl.textContent = command;
|
||||
if (indicator) indicator.className = 'status-indicator status-running';
|
||||
if (titleEl) {
|
||||
titleEl.innerHTML = '<span id="status-indicator" class="status-indicator status-running"></span>Current Command';
|
||||
}
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
const completedEl = document.getElementById('completed-count');
|
||||
@@ -1239,14 +1310,9 @@ class TestExecutor {
|
||||
const progressEl = document.getElementById('progress-fill');
|
||||
if (progressEl) progressEl.style.width = progress + '%';
|
||||
|
||||
// Update status indicator (only if not paused)
|
||||
const indicator = document.getElementById('status-indicator');
|
||||
if (indicator && !window.playwrongPaused) {
|
||||
indicator.className = 'status-indicator status-running';
|
||||
}
|
||||
|
||||
}, {
|
||||
command: this.formatCommandForOutput(command),
|
||||
nextInteractiveCommand: nextInteractiveCommand ? this.formatCommandForOutput(nextInteractiveCommand) : null,
|
||||
completed: this.completedCommands,
|
||||
total: this.totalCommands,
|
||||
elapsed: elapsedSeconds,
|
||||
@@ -1925,9 +1991,27 @@ class TestExecutor {
|
||||
// Ignore errors when closing status browser
|
||||
}
|
||||
}
|
||||
if (this.page) await this.page.close();
|
||||
if (this.context) await this.context.close();
|
||||
if (this.browser) await this.browser.close();
|
||||
if (this.page) {
|
||||
try {
|
||||
await this.page.close();
|
||||
} catch (error) {
|
||||
// Ignore errors when closing page
|
||||
}
|
||||
}
|
||||
if (this.context) {
|
||||
try {
|
||||
await this.context.close();
|
||||
} catch (error) {
|
||||
// Ignore errors when closing context
|
||||
}
|
||||
}
|
||||
if (this.browser) {
|
||||
try {
|
||||
await this.browser.close();
|
||||
} catch (error) {
|
||||
// Ignore errors when closing browser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolveEnvironmentVariables(value) {
|
||||
|
||||
@@ -62,12 +62,12 @@ sleep 100 "stadt fill"
|
||||
wait element=textarea name="note"
|
||||
scroll element=textarea name="note"
|
||||
fill element=textarea name="note" value="Musteranmerkung"
|
||||
sleep 1000000 "note fill"
|
||||
sleep 100 "note fill"
|
||||
scroll element=button childText="Bestellung abschließen"
|
||||
wait element=label childText="Bestimmungen"
|
||||
click element=label childText="Bestimmungen"
|
||||
sleep 100 "checkbox checked"
|
||||
wait element=button childText="Bestellung abschließen"
|
||||
click element=button childText="Bestellung abschließen"
|
||||
sleep 300000 "order completion"
|
||||
sleep 3000 "order completion"
|
||||
|
||||
|
||||
77
step1_dhl.test
Normal file
77
step1_dhl.test
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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://dev.seedheads.de"
|
||||
sleep 200 "page load"
|
||||
wait element=a href=/Kategorie/Seeds
|
||||
click element=a href=/Kategorie/Seeds
|
||||
sleep 200 "seed click"
|
||||
wait element=button childText="In den Korb"
|
||||
click element=button childText="In den Korb"
|
||||
sleep 200 "in korb click"
|
||||
wait element=span class="MuiBadge-badge" childText="1"
|
||||
click element=button child=span(class="MuiBadge-badge" childText="1")
|
||||
sleep 200 "korb click"
|
||||
wait element=button childText="Weiter zur Kasse"
|
||||
click element=button childText="Weiter zur Kasse"
|
||||
sleep 200 "weiter click"
|
||||
wait element=input type="email"
|
||||
fill element=input type="email" value="autotest@growheads.de"
|
||||
sleep 200 "email fill"
|
||||
wait element=input type="password"
|
||||
fill element=input type="password" value="$PASSWORD"
|
||||
sleep 200 "password fill"
|
||||
wait element=button childText="ANMELDEN" class="MuiButton-fullWidth"
|
||||
click element=button childText="ANMELDEN" class="MuiButton-fullWidth"
|
||||
sleep 2000 "anmelden click"
|
||||
#dump "isServer"
|
||||
jumpIfNot element=span childText="Server-Warenkorb löschen" jump=2
|
||||
click element=button childText="Weiter"
|
||||
sleep 2000 "anmelden click"
|
||||
|
||||
# Part 2 - Fill in the checkout form
|
||||
|
||||
wait element=input name="firstName"
|
||||
scroll element=span childText="Vorname"
|
||||
fill element=input name="firstName" value="Max"
|
||||
sleep 100 "vorname fill"
|
||||
wait element=input name="lastName"
|
||||
fill element=input name="lastName" value="Muster"
|
||||
sleep 100 "nachname fill"
|
||||
wait element=input name="street"
|
||||
fill element=input name="street" value="Muster"
|
||||
sleep 100 "strasse fill"
|
||||
wait element=input name="houseNumber"
|
||||
fill element=input name="houseNumber" value="420"
|
||||
sleep 100 "hausnummer fill"
|
||||
wait element=input name="postalCode"
|
||||
fill element=input name="postalCode" value="42023"
|
||||
sleep 100 "plz fill"
|
||||
wait element=input name="city"
|
||||
fill element=input name="city" value="Muster"
|
||||
sleep 100 "stadt fill"
|
||||
|
||||
wait element=textarea name="note"
|
||||
scroll element=textarea name="note"
|
||||
fill element=textarea name="note" value="Musteranmerkung"
|
||||
sleep 100 "note fill"
|
||||
wait element=input id="DHL" name="deliveryMethod" value="DHL" type="radio"
|
||||
scroll element=input id="DHL" name="deliveryMethod" value="DHL" type="radio"
|
||||
click element=input id="DHL" name="deliveryMethod" value="DHL" type="radio"
|
||||
sleep 100 "dhl click"
|
||||
wait element=label childText="Bestimmungen"
|
||||
scroll element=button childText="Bestellung abschließen"
|
||||
click element=label childText="Bestimmungen"
|
||||
sleep 100 "checkbox checked"
|
||||
wait element=button childText="Bestellung abschließen"
|
||||
click element=button childText="Bestellung abschließen"
|
||||
sleep 3000 "order completion"
|
||||
|
||||
80
step1_dhl_nach.test
Normal file
80
step1_dhl_nach.test
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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://dev.seedheads.de"
|
||||
sleep 200 "page load"
|
||||
wait element=a href=/Kategorie/Seeds
|
||||
click element=a href=/Kategorie/Seeds
|
||||
sleep 200 "seed click"
|
||||
wait element=button childText="In den Korb"
|
||||
click element=button childText="In den Korb"
|
||||
sleep 200 "in korb click"
|
||||
wait element=span class="MuiBadge-badge" childText="1"
|
||||
click element=button child=span(class="MuiBadge-badge" childText="1")
|
||||
sleep 200 "korb click"
|
||||
wait element=button childText="Weiter zur Kasse"
|
||||
click element=button childText="Weiter zur Kasse"
|
||||
sleep 200 "weiter click"
|
||||
wait element=input type="email"
|
||||
fill element=input type="email" value="autotest@growheads.de"
|
||||
sleep 200 "email fill"
|
||||
wait element=input type="password"
|
||||
fill element=input type="password" value="$PASSWORD"
|
||||
sleep 200 "password fill"
|
||||
wait element=button childText="ANMELDEN" class="MuiButton-fullWidth"
|
||||
click element=button childText="ANMELDEN" class="MuiButton-fullWidth"
|
||||
sleep 2000 "anmelden click"
|
||||
#dump "isServer"
|
||||
jumpIfNot element=span childText="Server-Warenkorb löschen" jump=2
|
||||
click element=button childText="Weiter"
|
||||
sleep 2000 "anmelden click"
|
||||
|
||||
# Part 2 - Fill in the checkout form
|
||||
|
||||
wait element=input name="firstName"
|
||||
scroll element=span childText="Vorname"
|
||||
fill element=input name="firstName" value="Max"
|
||||
sleep 100 "vorname fill"
|
||||
wait element=input name="lastName"
|
||||
fill element=input name="lastName" value="Muster"
|
||||
sleep 100 "nachname fill"
|
||||
wait element=input name="street"
|
||||
fill element=input name="street" value="Muster"
|
||||
sleep 100 "strasse fill"
|
||||
wait element=input name="houseNumber"
|
||||
fill element=input name="houseNumber" value="420"
|
||||
sleep 100 "hausnummer fill"
|
||||
wait element=input name="postalCode"
|
||||
fill element=input name="postalCode" value="42023"
|
||||
sleep 100 "plz fill"
|
||||
wait element=input name="city"
|
||||
fill element=input name="city" value="Muster"
|
||||
sleep 100 "stadt fill"
|
||||
|
||||
wait element=textarea name="note"
|
||||
scroll element=textarea name="note"
|
||||
fill element=textarea name="note" value="Musteranmerkung"
|
||||
sleep 100 "note fill"
|
||||
wait element=input id="DHL" name="deliveryMethod" value="DHL" type="radio"
|
||||
scroll element=input id="DHL" name="deliveryMethod" value="DHL" type="radio"
|
||||
click element=input id="DHL" name="deliveryMethod" value="DHL" type="radio"
|
||||
sleep 100 "dhl click"
|
||||
wait element=input id="onDelivery" name="paymentMethod" value="onDelivery" type="radio"
|
||||
scroll element=input id="onDelivery" name="paymentMethod" value="onDelivery" type="radio"
|
||||
click element=input id="onDelivery" name="paymentMethod" value="onDelivery" type="radio"
|
||||
sleep 100 "on delivery click"
|
||||
scroll element=button childText="Bestellung abschließen"
|
||||
click element=label childText="Bestimmungen"
|
||||
sleep 100 "checkbox checked"
|
||||
wait element=button childText="Bestellung abschließen"
|
||||
click element=button childText="Bestellung abschließen"
|
||||
sleep 3000 "order completion"
|
||||
|
||||
77
step1_dpd.test
Normal file
77
step1_dpd.test
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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://dev.seedheads.de"
|
||||
sleep 200 "page load"
|
||||
wait element=a href=/Kategorie/Seeds
|
||||
click element=a href=/Kategorie/Seeds
|
||||
sleep 200 "seed click"
|
||||
wait element=button childText="In den Korb"
|
||||
click element=button childText="In den Korb"
|
||||
sleep 200 "in korb click"
|
||||
wait element=span class="MuiBadge-badge" childText="1"
|
||||
click element=button child=span(class="MuiBadge-badge" childText="1")
|
||||
sleep 200 "korb click"
|
||||
wait element=button childText="Weiter zur Kasse"
|
||||
click element=button childText="Weiter zur Kasse"
|
||||
sleep 200 "weiter click"
|
||||
wait element=input type="email"
|
||||
fill element=input type="email" value="autotest@growheads.de"
|
||||
sleep 200 "email fill"
|
||||
wait element=input type="password"
|
||||
fill element=input type="password" value="$PASSWORD"
|
||||
sleep 200 "password fill"
|
||||
wait element=button childText="ANMELDEN" class="MuiButton-fullWidth"
|
||||
click element=button childText="ANMELDEN" class="MuiButton-fullWidth"
|
||||
sleep 2000 "anmelden click"
|
||||
#dump "isServer"
|
||||
jumpIfNot element=span childText="Server-Warenkorb löschen" jump=2
|
||||
click element=button childText="Weiter"
|
||||
sleep 2000 "anmelden click"
|
||||
|
||||
# Part 2 - Fill in the checkout form
|
||||
|
||||
wait element=input name="firstName"
|
||||
scroll element=span childText="Vorname"
|
||||
fill element=input name="firstName" value="Max"
|
||||
sleep 100 "vorname fill"
|
||||
wait element=input name="lastName"
|
||||
fill element=input name="lastName" value="Muster"
|
||||
sleep 100 "nachname fill"
|
||||
wait element=input name="street"
|
||||
fill element=input name="street" value="Muster"
|
||||
sleep 100 "strasse fill"
|
||||
wait element=input name="houseNumber"
|
||||
fill element=input name="houseNumber" value="420"
|
||||
sleep 100 "hausnummer fill"
|
||||
wait element=input name="postalCode"
|
||||
fill element=input name="postalCode" value="42023"
|
||||
sleep 100 "plz fill"
|
||||
wait element=input name="city"
|
||||
fill element=input name="city" value="Muster"
|
||||
sleep 100 "stadt fill"
|
||||
|
||||
wait element=textarea name="note"
|
||||
scroll element=textarea name="note"
|
||||
fill element=textarea name="note" value="Musteranmerkung"
|
||||
sleep 100 "note fill"
|
||||
wait element=input id="DPD" name="deliveryMethod" value="DPD" type="radio"
|
||||
scroll element=input id="DPD" name="deliveryMethod" value="DPD" type="radio"
|
||||
click element=input id="DPD" name="deliveryMethod" value="DPD" type="radio"
|
||||
sleep 100 "dpd click"
|
||||
wait element=label childText="Bestimmungen"
|
||||
scroll element=button childText="Bestellung abschließen"
|
||||
click element=label childText="Bestimmungen"
|
||||
sleep 100 "checkbox checked"
|
||||
wait element=button childText="Bestellung abschließen"
|
||||
click element=button childText="Bestellung abschließen"
|
||||
sleep 3000 "order completion"
|
||||
|
||||
Reference in New Issue
Block a user