Refactor test scripts by removing unused files, updating test commands, and adding new functionality for window switching and variable extraction

This commit is contained in:
seb
2025-07-17 08:16:17 +02:00
parent 49270f8d2f
commit fe4ce936c6
9 changed files with 223 additions and 95 deletions

View File

@@ -92,12 +92,14 @@ async function main() {
console.log(' --headed Run in headed mode (show browser)');
console.log(' --headless Run in headless mode (default)');
console.log(' --enable-animations Enable CSS animations (default: disabled)');
console.log(' --full-page Take full page screenshots instead of viewport only');
console.log(' --lint Run linter before execution');
console.log(' --strict Treat linter warnings as errors');
console.log('Examples:');
console.log(' node src/cli.js tests/example.test Chrome');
console.log(' node src/cli.js tests/example.test Chrome --headed');
console.log(' node src/cli.js tests/example.test Chrome --headed --enable-animations');
console.log(' node src/cli.js tests/example.test Chrome --headed --full-page');
console.log(' node src/cli.js tests/example.test Chrome --lint --strict');
process.exit(1);
}
@@ -106,6 +108,7 @@ async function main() {
const profile = args[1] || 'Chrome';
const headless = !args.includes('--headed');
const disableAnimations = !args.includes('--enable-animations');
const fullPageScreenshots = args.includes('--full-page');
const lint = args.includes('--lint');
const strict = args.includes('--strict');
@@ -114,7 +117,8 @@ async function main() {
process.exit(1);
}
const runner = new TestRunner({ headless, disableAnimations, lint, strict });
const runner = new TestRunner({ headless, disableAnimations, fullPageScreenshots, lint, strict });
await runner.runTestFile(testFile, profile);
}

View File

@@ -15,6 +15,8 @@ class TestExecutor {
this.disableAnimations = options.disableAnimations !== false; // Default to disable animations
this.fullPageScreenshots = options.fullPageScreenshots || false; // Default to viewport screenshots
this.enableScreenshots = options.enableScreenshots !== false; // Default to enable screenshots
this.variables = {}; // Store extracted variables
this.profiles = {
Chrome: {
viewport: { width: 1280, height: 720 },
@@ -148,7 +150,8 @@ class TestExecutor {
break;
case 'open':
await this.page.goto(command.url, { waitUntil: 'networkidle' });
const resolvedUrl = this.resolveEnvironmentVariables(command.url);
await this.page.goto(resolvedUrl, { waitUntil: 'networkidle' });
// Small delay to ensure page is stable
if (!this.headless) {
await this.page.waitForTimeout(200);
@@ -203,6 +206,18 @@ class TestExecutor {
await this.sleep(command);
break;
case 'switchToNewWindow':
await this.switchToNewWindow();
break;
case 'switchToTab':
await this.switchToTab(command);
break;
case 'extract':
await this.extractToVariable(command);
break;
default:
console.warn(`Unknown command type: ${command.type}`);
}
@@ -648,6 +663,75 @@ class TestExecutor {
});
}
async switchToNewWindow() {
console.log('🔄 Switching to new tab...');
// Get all current pages/tabs
const pages = this.context.pages();
if (pages.length < 2) {
throw new Error('No additional tabs found to switch to');
}
// Switch to the last (most recently opened) tab
const newTab = pages[pages.length - 1];
// Wait for the tab to be ready
await newTab.waitForLoadState('networkidle');
// Switch to the new tab
this.page = newTab;
console.log('🔄 Switched to new tab');
}
async switchToTab(command) {
const tabIndex = command.tabIndex;
if (tabIndex === undefined || tabIndex < 0 || tabIndex >= this.context.pages().length) {
throw new Error(`Invalid tab index: ${tabIndex}`);
}
const targetPage = this.context.pages()[tabIndex];
this.page = targetPage;
console.log(`🔄 Switched to tab index ${tabIndex}`);
}
async extractToVariable(command) {
const selector = this.buildSelector(command);
const attribute = command.attribute;
const variableName = command.variableName;
if (!variableName) {
throw new Error('extract command requires a variableName');
}
let value = '';
try {
const element = await this.page.locator(selector).first();
if (attribute === 'text') {
value = await element.textContent();
} else if (attribute === 'innerHTML') {
value = await element.innerHTML();
} else {
// Extract attribute value
value = await element.getAttribute(attribute);
}
if (value === null || value === undefined) {
throw new Error(`Attribute "${attribute}" not found on element`);
}
console.log(`📋 Extracted ${attribute} from "${selector}" to variable "${variableName}": "${value}"`);
} catch (e) {
console.warn(`Could not extract ${attribute} from "${selector}" to variable "${variableName}":`, e.message);
throw new Error(`Could not extract ${attribute} from "${selector}" to variable "${variableName}"`);
}
this.variables[variableName] = value;
console.log(`✅ Variable "${variableName}" set to: "${value}"`);
}
formatCommandForOutput(command) {
switch (command.type) {
case 'use':
@@ -671,6 +755,12 @@ class TestExecutor {
return `break "${command.message}"`;
case 'sleep':
return command.message ? `sleep ${command.milliseconds} "${command.message}"` : `sleep ${command.milliseconds}`;
case 'switchToNewWindow':
return 'switchToNewWindow';
case 'switchToTab':
return `switchToTab tabIndex=${command.tabIndex}`;
case 'extract':
return `extract ${this.formatSelector(command)} variableName="${command.variableName}"`;
default:
return `${command.type} ${JSON.stringify(command)}`;
}
@@ -1141,11 +1231,17 @@ class TestExecutor {
return value;
}
// Replace $VARIABLE or ${VARIABLE} with environment variable values
// Replace $VARIABLE or ${VARIABLE} with environment variable values or stored variables
return value.replace(/\$\{?([A-Z_][A-Z0-9_]*)\}?/g, (match, varName) => {
// First check stored variables
if (this.variables[varName] !== undefined) {
return this.variables[varName];
}
// Then check environment variables
const envValue = process.env[varName];
if (envValue === undefined) {
console.warn(`Warning: Environment variable ${varName} is not defined`);
console.warn(`Warning: Variable ${varName} is not defined in stored variables or environment`);
return match; // Return original if not found
}
return envValue;

View File

@@ -6,7 +6,7 @@ class TestLinter {
this.info = [];
// Valid commands
this.validCommands = ['use', 'open', 'wait', 'click', 'scroll', 'fill', 'break', 'sleep', 'dump'];
this.validCommands = ['use', 'open', 'wait', 'click', 'scroll', 'fill', 'break', 'sleep', 'dump', 'switchToNewWindow', 'switchToTab', 'extract'];
// Valid HTML elements
this.validElements = [
@@ -29,7 +29,10 @@ class TestLinter {
'fill': ['element', 'value'],
'break': [],
'sleep': ['milliseconds'],
'dump': ['name']
'dump': ['name'],
'switchToNewWindow': [],
'switchToTab': ['tabIndex'],
'extract': ['element', 'attribute']
};
// Initialize rules
@@ -252,6 +255,19 @@ class TestLinter {
for (const line of lines) {
const command = line.cleaned.split(' ')[0];
// Handle multi-word commands
if (line.cleaned.startsWith('switch to new window')) {
continue; // This is valid
}
if (line.cleaned.startsWith('extract ')) {
continue; // This is valid
}
if (line.cleaned.startsWith('switch to tab')) {
continue; // This is valid
}
if (!this.validCommands.includes(command)) {
this.addError(`Invalid command '${command}'. Valid commands: ${this.validCommands.join(', ')}`, line.lineNumber);
}
@@ -312,6 +328,10 @@ class TestLinter {
if (!line.cleaned.match(/\d+/)) {
this.addError(`Command '${command}' requires numeric milliseconds`, line.lineNumber);
}
} else if (reqParam === 'tabIndex' && command === 'switchToTab') {
if (!line.cleaned.match(/\d+/)) {
this.addError(`Command '${command}' requires numeric tabIndex`, line.lineNumber);
}
} else if (!params[reqParam]) {
this.addError(`Command '${command}' missing required parameter '${reqParam}'`, line.lineNumber);
}

View File

@@ -186,6 +186,50 @@ class TestParser {
}
}
// Parse switch to new window command
if (line.startsWith('switch to new window')) {
return {
type: 'switchToNewWindow'
};
}
// Parse switch to tab command: switch to tab 0
if (line.startsWith('switch to tab ')) {
const match = line.match(/switch to tab (\d+)/);
if (match) {
return {
type: 'switchToTab',
tabIndex: parseInt(match[1])
};
}
}
// Parse extract command: extract href from element=a target="_blank" to variable="ORDER_URL"
if (line.startsWith('extract ')) {
const match = line.match(/extract\s+(\w+)\s+from\s+(.+?)\s+to\s+variable="([^"]+)"/);
if (match) {
const attribute = match[1];
const selectorPart = match[2];
const variableName = match[3];
const params = this.parseParameters(selectorPart);
return {
type: 'extract',
attribute: attribute,
variableName: variableName,
element: params.element,
name: params.name,
id: params.id,
class: params.class,
href: params.href,
htmlType: params.type,
child: params.child,
childText: params.childText,
target: params.target
};
}
}
return null;
}