Add 'jumpIf' and 'jumpIfNot' commands to README and executor; implement parsing, execution, and linter support for conditional command jumps in test scripts.

This commit is contained in:
seb
2025-07-17 14:06:41 +02:00
parent 41e4424650
commit 85f7f81236
9 changed files with 454 additions and 11 deletions

View File

@@ -43,13 +43,21 @@ class TestExecutor {
await this.updateStatus({ type: 'initializing' }, false);
}
for (const command of commands) {
for (let i = 0; i < commands.length; i++) {
const command = commands[i];
// Update status before executing command
if (!this.headless) {
await this.updateStatus(command, false);
}
await this.executeCommand(command);
const jumpCount = await this.executeCommand(command);
// Handle jump commands
if (jumpCount > 0) {
console.log(`🔄 Jumping ${jumpCount} commands`);
i += jumpCount; // Skip the specified number of commands
}
// Update status after completing command
if (!this.headless) {
@@ -92,11 +100,21 @@ class TestExecutor {
reducedMotion: 'reduce',
// Force consistent viewport
screen: profile.viewport,
deviceScaleFactor: 1
deviceScaleFactor: 1,
// Allow popups to be opened
javaScriptEnabled: true,
// Handle popups properly
ignoreHTTPSErrors: true
});
this.page = await this.context.newPage();
// Handle popup events for follow command
this.page.on('popup', async (popup) => {
console.log('🔄 Popup detected, adding to context');
// The popup is automatically added to the context.pages() array
});
// Create status window for headed mode AFTER main page so it appears on top
if (!this.headless) {
await this.createStatusWindow();
@@ -239,9 +257,17 @@ class TestExecutor {
await this.extractToVariable(command);
break;
case 'jumpIf':
return await this.jumpIf(command);
case 'jumpIfNot':
return await this.jumpIfNot(command);
default:
console.warn(`Unknown command type: ${command.type}`);
}
return 0; // No jump by default
}
async waitForElement(command) {
@@ -922,15 +948,69 @@ class TestExecutor {
async followToNewWindow() {
console.log('🔄 Switching to new tab...');
// Get all current pages/tabs
const pages = this.context.pages();
console.log(`🔄 Debug: Found ${pages.length} pages total`);
pages.forEach((page, index) => {
console.log(`🔄 Page ${index}: ${page.url()}`);
});
if (pages.length < 2) {
throw new Error('No additional tabs found to switch to');
// Check if we already have multiple pages (popup already created)
if (pages.length >= 2) {
// Find the newest tab (not the current one)
let newTab = null;
const currentUrl = this.page.url();
// Look for a tab that's different from the current one
for (let i = pages.length - 1; i >= 0; i--) {
if (pages[i].url() !== currentUrl) {
newTab = pages[i];
break;
}
}
if (newTab) {
console.log(`🔄 Found existing new tab: ${newTab.url()}`);
// 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');
return;
}
}
// Switch to the last (most recently opened) tab
const newTab = pages[pages.length - 1];
// Fallback: Wait for a new tab to be created
const initialPageCount = pages.length;
let newTab = null;
let retryCount = 0;
const maxRetries = 20; // 10 seconds total (500ms * 20)
while (retryCount < maxRetries) {
const currentPages = this.context.pages();
if (currentPages.length > initialPageCount) {
// New tab found, get the most recently opened one
newTab = currentPages[currentPages.length - 1];
break;
}
// Wait a bit before retrying
await this.page.waitForTimeout(500);
retryCount++;
if (retryCount % 5 === 0) {
console.log(`🔄 Still waiting for new tab... (${retryCount}/${maxRetries})`);
}
}
if (!newTab) {
throw new Error('No additional tabs found to switch to after waiting');
}
console.log(`🔄 New tab found: ${newTab.url()}`);
// Wait for the tab to be ready
await newTab.waitForLoadState('networkidle');
@@ -988,6 +1068,34 @@ class TestExecutor {
console.log(`✅ Variable "${variableName}" set to: "${value}"`);
}
async jumpIf(command) {
const selector = this.buildSelector(command);
try {
// Check if element exists (with a short timeout)
await this.page.locator(selector).first().waitFor({ timeout: 1000 });
console.log(`🔄 jumpIf: Element found, jumping ${command.jumpCount} commands`);
return command.jumpCount;
} catch (error) {
console.log(`🔄 jumpIf: Element not found, continuing normally`);
return 0;
}
}
async jumpIfNot(command) {
const selector = this.buildSelector(command);
try {
// Check if element exists (with a short timeout)
await this.page.locator(selector).first().waitFor({ timeout: 1000 });
console.log(`🔄 jumpIfNot: Element found, continuing normally`);
return 0;
} catch (error) {
console.log(`🔄 jumpIfNot: Element not found, jumping ${command.jumpCount} commands`);
return command.jumpCount;
}
}
formatCommandForOutput(command) {
switch (command.type) {
case 'use':
@@ -1017,6 +1125,10 @@ class TestExecutor {
return `switchToTab tabIndex=${command.tabIndex}`;
case 'extract':
return `extract ${this.formatSelector(command)} variableName="${command.variableName}"`;
case 'jumpIf':
return `jumpIf ${this.formatSelector(command)} jump=${command.jumpCount}`;
case 'jumpIfNot':
return `jumpIfNot ${this.formatSelector(command)} jump=${command.jumpCount}`;
default:
return `${command.type} ${JSON.stringify(command)}`;
}