# Instructions - Following Playwright test failed. - Explain why, be concise, respect Playwright best practices. - Provide a snippet of code with the fix, if possible. # Test info - Name: launch_readiness.spec.ts >> Launch Readiness E2E & Visual Verification >> 01. Should render desktop app with Free Tier AdBanner and Support button - Location: tests\e2e\launch_readiness.spec.ts:72:7 # Error details ``` TimeoutError: electronApplication.firstWindow: Timeout 30000ms exceeded while waiting for event "window" ``` ``` "afterAll" hook timeout of 60000ms exceeded. ``` # Test source ```ts 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { test, expect, _electron as electron } from '@playwright/test'; 4 | 5 | const SCREENSHOT_DIR = 'C:/Users/encep/.gemini/antigravity-cli/brain/3ab7ae82-5634-41d5-93a7-3c12c22503e2/screenshots'; 6 | 7 | test.describe('Launch Readiness E2E & Visual Verification', () => { 8 | test.describe.configure({ mode: 'serial' }); 9 | let electronApp: any; 10 | let window: any; 11 | 12 | test.beforeAll(async () => { 13 | if (!fs.existsSync(SCREENSHOT_DIR)) { 14 | fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); 15 | } 16 | 17 | electronApp = await electron.launch({ 18 | args: [ 19 | 'out/main/index.js', 20 | '--disable-gpu', 21 | '--no-sandbox', 22 | '--user-data-dir=C:/Users/encep/AppData/Local/Temp/playwright-d3ro-launch-test-' + Date.now(), 23 | ], 24 | env: { 25 | ...process.env, 26 | NODE_ENV: 'test', 27 | }, 28 | }); 29 | 30 | // Poll for main window (excluding popup windows) 31 | for (let i = 0; i < 40; i++) { 32 | for (const w of electronApp.windows()) { 33 | try { 34 | const url = w.url(); 35 | if (url && url.includes('index.html') && !url.includes('popups/')) { 36 | window = w; 37 | break; 38 | } 39 | } catch { 40 | // ignore 41 | } 42 | } 43 | if (window) break; 44 | await new Promise((r) => setTimeout(r, 500)); 45 | } 46 | 47 | if (!window) { 48 | window = electronApp.windows()[0] || await electronApp.firstWindow(); 49 | } 50 | await window.waitForLoadState('domcontentloaded'); 51 | 52 | // Bypass onboarding via IPC config set 53 | await window.evaluate(async () => { 54 | if (window.electronAPI && window.electronAPI.config) { 55 | await window.electronAPI.config.set({ key: 'onboardingCompleted', value: true }); 56 | await window.electronAPI.config.set({ key: 'appUsageMode', value: 'online' }); 57 | } 58 | }); 59 | 60 | await window.waitForTimeout(500); 61 | await window.reload(); 62 | await window.waitForLoadState('domcontentloaded'); 63 | await window.waitForTimeout(1500); 64 | }); 65 | > 66 | test.afterAll(async () => { | ^ "afterAll" hook timeout of 60000ms exceeded. 67 | if (electronApp) { 68 | await electronApp.close(); 69 | } 70 | }); 71 | 72 | test('01. Should render desktop app with Free Tier AdBanner and Support button', async () => { 73 | // Check if sidebar has Customer Support button 74 | const supportBtn = window.locator('text=/^(Customer Support|고객지원)$/').first(); 75 | await expect(supportBtn).toBeVisible({ timeout: 10000 }); 76 | 77 | // Check if Free Tier AdBanner is visible 78 | const adBanner = window.locator('text=/Cursor AI/').first(); 79 | await expect(adBanner).toBeVisible({ timeout: 10000 }); 80 | 81 | // Take screenshot of main window with AdBanner and sidebar 82 | await window.screenshot({ path: path.join(SCREENSHOT_DIR, '01_desktop_main_with_adbanner.png') }); 83 | }); 84 | 85 | test('02. Should open SupportModal and interact with AI Customer Assistant', async () => { 86 | // Click Customer Support in sidebar 87 | await window.locator('text=/^(Customer Support|고객지원)$/').first().click(); 88 | await window.waitForTimeout(500); 89 | 90 | // Verify modal header is visible 91 | await expect(window.locator('text=/D3RO Voice Customer Assistance/').first()).toBeVisible(); 92 | 93 | // Click quick prompt "🎤 마이크 인식 오류" 94 | const micPrompt = window.locator('text=/마이크 인식/').first(); 95 | if (await micPrompt.isVisible()) { 96 | await micPrompt.click(); 97 | await window.waitForTimeout(800); 98 | } 99 | 100 | // Capture screenshot of SupportModal with AI chat 101 | await window.screenshot({ path: path.join(SCREENSHOT_DIR, '02_support_modal_ai_chat.png') }); 102 | 103 | // Switch to 7-Day Refund Tab (tab 3) 104 | const refundTab = window.locator('text=/7일 자동 환불/').first(); 105 | await refundTab.click(); 106 | await window.waitForTimeout(500); 107 | 108 | // Click "환불 자격 자동 조회하기" 109 | const checkRefundBtn = window.locator('button:has-text("환불 자격 자동 조회하기")').first(); 110 | if (await checkRefundBtn.isVisible()) { 111 | await checkRefundBtn.click(); 112 | await window.waitForTimeout(1000); 113 | } 114 | 115 | // Capture screenshot of refund eligibility result 116 | await window.screenshot({ path: path.join(SCREENSHOT_DIR, '03_support_modal_refund_check.png') }); 117 | 118 | // Close Support Modal 119 | await window.keyboard.press('Escape'); 120 | await window.waitForTimeout(500); 121 | }); 122 | 123 | test('03. Should open CheckoutModal and display Toss Payments / Stripe options', async () => { 124 | // Trigger checkout via "Remove ads with Pro" link in AdBanner 125 | const removeAdsLink = window.locator('text=/Remove ads with Pro/').first(); 126 | if (await removeAdsLink.isVisible()) { 127 | await removeAdsLink.click(); 128 | await window.waitForTimeout(600); 129 | 130 | // Verify Checkout Modal is visible 131 | await expect(window.locator('text=/D3RO Voice Secure Checkout/').first()).toBeVisible(); 132 | 133 | // Capture screenshot of CheckoutModal 134 | await window.screenshot({ path: path.join(SCREENSHOT_DIR, '04_checkout_modal_toss_stripe.png') }); 135 | 136 | // Close modal 137 | await window.keyboard.press('Escape'); 138 | await window.waitForTimeout(400); 139 | } 140 | }); 141 | }); 142 | ```