Skip to main content
Glama
composer-ui-automation.ts9.78 kB
/** * Composer UI Automation Module * Extends RealBrowserManager with specific UI automation methods for EuConquisto Composer * @version 2.0.0 * @created 2025-07-02 */ import { Page } from 'playwright'; export interface ComposerSelectors { // Authentication jwtRedirectUrl: string; // Navigation newCompositionButton: string; saveButton: string; // Composer UI composerEditor: string; elementSidebar: string; // localStorage storageKey: string; } export class ComposerUIAutomation { private selectors: ComposerSelectors = { // JWT redirect server jwtRedirectUrl: 'http://localhost:8080/composer', // UI selectors based on testing results newCompositionButton: 'button:has-text("Nova composição")', saveButton: 'button[title*="Salvar composição"]', // Composer interface composerEditor: '[data-testid="composer-editor"], #composer-editor, .composer-content', elementSidebar: '[data-testid="element-sidebar"], .element-sidebar, .sidebar', // localStorage key storageKey: 'rdp-composer-data' }; /** * Authenticate using JWT redirect server */ async authenticateViaJWT(page: Page): Promise<boolean> { try { console.log('🔐 Authenticating via JWT redirect server...'); // Navigate to JWT redirect server await page.goto(this.selectors.jwtRedirectUrl, { waitUntil: 'networkidle', timeout: 30000 }); // Wait for redirect to composer await page.waitForURL('**/composer.euconquisto.com/**', { timeout: 15000 }); console.log('✅ Authentication successful'); return true; } catch (error) { console.error('❌ Authentication failed:', error); return false; } } /** * Create a new blank composition */ async createBlankComposition(page: Page): Promise<boolean> { try { console.log('📝 Creating blank composition...'); // Wait for page to be fully loaded await page.waitForLoadState('networkidle'); // Click "Nova composição" button await page.waitForSelector(this.selectors.newCompositionButton, { state: 'visible', timeout: 10000 }); await page.click(this.selectors.newCompositionButton); // Wait for navigation context destruction (known issue) await page.waitForTimeout(3000); // Wait for composer editor to load await page.waitForSelector(this.selectors.composerEditor, { state: 'visible', timeout: 15000 }); console.log('✅ Blank composition created'); return true; } catch (error) { console.error('❌ Failed to create blank composition:', error); return false; } } /** * Extract current composition data from localStorage */ async extractLocalStorageData(page: Page): Promise<any> { try { console.log('📊 Extracting localStorage data...'); const data = await page.evaluate((storageKey) => { const stored = localStorage.getItem(storageKey); return stored ? JSON.parse(stored) : null; }, this.selectors.storageKey); if (data) { console.log('✅ localStorage data extracted'); console.log(` - Composition ID: ${data.composition?.id || 'N/A'}`); console.log(` - Elements count: ${data.composition?.elements?.length || 0}`); } else { console.log('⚠️ No data found in localStorage'); } return data; } catch (error) { console.error('❌ Failed to extract localStorage data:', error); return null; } } /** * Inject enriched composition data into localStorage */ async injectCompositionData(page: Page, compositionData: any): Promise<boolean> { try { console.log('💉 Injecting composition data into localStorage...'); const injected = await page.evaluate((storageKey, data) => { try { localStorage.setItem(storageKey, JSON.stringify(data)); console.log('localStorage injection complete'); return true; } catch (e) { console.error('localStorage injection failed:', e); return false; } }, this.selectors.storageKey, compositionData); if (injected) { console.log('✅ Composition data injected successfully'); // Try to trigger content loading await this.triggerContentLoading(page); } return injected; } catch (error) { console.error('❌ Failed to inject composition data:', error); return false; } } /** * Trigger content loading from localStorage * This is the investigation area - different strategies to try */ async triggerContentLoading(page: Page): Promise<boolean> { console.log('🔄 Attempting to trigger content loading...'); try { // Strategy 1: Dispatch storage event await page.evaluate(() => { window.dispatchEvent(new StorageEvent('storage', { key: 'rdp-composer-data', newValue: localStorage.getItem('rdp-composer-data'), url: window.location.href, storageArea: localStorage })); }); // Wait a bit to see if content loads await page.waitForTimeout(2000); // Strategy 2: Page reload (fallback) // Commented out for now - can be enabled if needed // await page.reload({ waitUntil: 'networkidle' }); console.log('✅ Content loading triggered'); return true; } catch (error) { console.error('⚠️ Content loading trigger failed:', error); return false; } } /** * Save the composition */ async saveComposition(page: Page): Promise<string | null> { try { console.log('💾 Saving composition...'); // Wait for save button to be enabled await page.waitForSelector(this.selectors.saveButton, { state: 'visible', timeout: 10000 }); // Get current URL before save const urlBefore = page.url(); // Click save button await page.click(this.selectors.saveButton); // Wait for URL to change (indicates save complete) await page.waitForFunction( (currentUrl) => window.location.href !== currentUrl, urlBefore, { timeout: 10000 } ); // Get new URL with encoded composition const savedUrl = page.url(); console.log('✅ Composition saved successfully'); console.log(`📎 URL: ${savedUrl}`); // Extract file_uid if present const fileUidMatch = savedUrl.match(/file_uid=([a-f0-9-]+)/); if (fileUidMatch) { console.log(`🔑 File UID: ${fileUidMatch[1]}`); } return savedUrl; } catch (error) { console.error('❌ Failed to save composition:', error); return null; } } /** * Merge blank composition structure with generated content */ mergeCompositionData(blankStructure: any, generatedContent: any): any { console.log('🔀 Merging composition data...'); // Start with the blank structure as base const merged = JSON.parse(JSON.stringify(blankStructure)); // Update with generated content if (generatedContent.composition) { merged.composition = { ...merged.composition, ...generatedContent.composition, // Preserve any system-generated IDs from blank structure id: merged.composition?.id || generatedContent.composition.id, // Merge elements array elements: generatedContent.composition.elements || [] }; } console.log('✅ Composition data merged'); console.log(` - Title: ${merged.composition?.title}`); console.log(` - Elements: ${merged.composition?.elements?.length}`); return merged; } /** * Complete workflow: authenticate, create, enrich, save */ async executeCompleteWorkflow( page: Page, generatedComposition: any ): Promise<{ success: boolean; url?: string; error?: string }> { try { console.log('🚀 Starting complete browser automation workflow...'); // Step 1: Authenticate const authSuccess = await this.authenticateViaJWT(page); if (!authSuccess) { return { success: false, error: 'Authentication failed' }; } // Step 2: Create blank composition const createSuccess = await this.createBlankComposition(page); if (!createSuccess) { return { success: false, error: 'Failed to create blank composition' }; } // Step 3: Extract blank structure const blankStructure = await this.extractLocalStorageData(page); if (!blankStructure) { return { success: false, error: 'Failed to extract blank structure' }; } // Step 4: Merge with generated content const enrichedData = this.mergeCompositionData(blankStructure, generatedComposition); // Step 5: Inject enriched data const injectSuccess = await this.injectCompositionData(page, enrichedData); if (!injectSuccess) { return { success: false, error: 'Failed to inject composition data' }; } // Step 6: Save composition const savedUrl = await this.saveComposition(page); if (!savedUrl) { return { success: false, error: 'Failed to save composition' }; } console.log('🎉 Workflow completed successfully!'); return { success: true, url: savedUrl }; } catch (error) { console.error('❌ Workflow failed:', error); return { success: false, error: error.message }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server