import { test, expect, _electron as electron, ElectronApplication, Page } from '@playwright/test'; import path from 'path'; import fs from 'fs'; const SCREENSHOT_DIR = path.resolve('C:/Users/encep/.gemini/antigravity/brain/bbff18a3-721d-4c43-989f-1d6964e15be7/screenshots'); test.describe.serial('Extreme Red Team - Cycle 3: Modals, Deep Configuration & System Shell', () => { let app: ElectronApplication; let window: Page; let testUserDataDir: string; const uncaughtExceptions: string[] = []; test.beforeAll(async () => { if (!fs.existsSync(SCREENSHOT_DIR)) { fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); } testUserDataDir = path.join( 'C:/Users/encep/AppData/Local/Temp', 'playwright-redteam-cycle3-' + Date.now() ); app = await electron.launch({ args: [ 'out/main/index.js', '--disable-gpu', '--no-sandbox', `--user-data-dir=${testUserDataDir}`, ], env: { ...process.env, NODE_ENV: 'test', }, }); // Find main window for (let i = 0; i < 50; i++) { for (const w of app.windows()) { try { const url = w.url(); if (url && url.includes('index.html') && !url.includes('popups/')) { window = w; break; } } catch { // window navigating } } if (window) break; await new Promise((r) => setTimeout(r, 400)); } if (!window) { window = app.windows()[0] || (await app.firstWindow()); } window.on('pageerror', (err) => { console.error('[PAGEERROR in Electron Window]:', err.message); uncaughtExceptions.push(err.message); }); await window.waitForLoadState('domcontentloaded'); // Bypass Onboarding modal for testing await window.evaluate(async () => { if (window.electronAPI?.config) { await window.electronAPI.config.set({ key: 'onboardingCompleted', value: true }); await window.electronAPI.config.set({ key: 'appUsageMode', value: 'online' }); await window.electronAPI.config.set({ key: 'llmBackend', value: 'online' }); } }); await window.waitForTimeout(500); await window.reload(); await window.waitForLoadState('domcontentloaded'); await window.waitForTimeout(1000); }); test.afterAll(async () => { if (app) { await app.close(); } }); test('RT-10: SettingsModal - Deep Tab Traversal, Live Theme Switching, Language Switch & Audio Test', async () => { // 1. Open SettingsModal from sidebar const settingsTrigger = window.getByText('설정', { exact: true }); await expect(settingsTrigger).toBeVisible({ timeout: 5000 }); await settingsTrigger.click(); await window.waitForTimeout(600); // Verify modal dialog opened await expect(window.getByText(/설정|SETTINGS/i).first()).toBeVisible({ timeout: 5000 }); // 2. Tab 0: General - Voice Mode Cards & Theme / Language await expect(window.getByText(/받아쓰기|Dictation/i).first()).toBeVisible(); await expect(window.getByText(/원터치|Hands-Free/i).first()).toBeVisible(); await expect(window.getByText(/자막|Caption/i).first()).toBeVisible(); // Test Theme Selector const themeSelect = window.locator('div[role="combobox"]').first(); if (await themeSelect.isVisible()) { await themeSelect.click(); await window.waitForTimeout(300); // Select Light theme const lightOption = window.getByRole('option', { name: /라이트|Light/i }); if (await lightOption.isVisible()) { await lightOption.click(); await window.waitForTimeout(400); } // Select Dark theme back await themeSelect.click(); await window.waitForTimeout(300); const darkOption = window.getByRole('option', { name: /다크|Dark/i }); if (await darkOption.isVisible()) { await darkOption.click(); await window.waitForTimeout(400); } } // 3. Tab 1: Audio Tab const audioTab = window.getByRole('tab', { name: /오디오|Audio/i }); await expect(audioTab).toBeVisible(); await audioTab.click(); await window.waitForTimeout(500); // Verify Audio Settings: Input device selector & Mic Test await expect(window.getByText(/입력 장치|마이크|Microphone/i).first()).toBeVisible(); const micTestBtn = window.getByRole('button', { name: /테스트|Test/i }).first(); if (await micTestBtn.isVisible()) { await micTestBtn.click(); await window.waitForTimeout(400); // Click again to stop test await micTestBtn.click(); await window.waitForTimeout(300); } // 4. Tab 2: STT Tab const sttTab = window.getByRole('tab', { name: 'STT' }); await expect(sttTab).toBeVisible(); await sttTab.click(); await window.waitForTimeout(500); // Verify STT Provider / Model configurations await expect(window.getByText(/STT 엔진|faster-whisper|모델|Provider/i).first()).toBeVisible(); // 5. Tab 3: LLM Tab const llmTab = window.getByRole('tab', { name: 'LLM' }); await expect(llmTab).toBeVisible(); await llmTab.click(); await window.waitForTimeout(500); // Verify Ollama Server URL input await expect(window.getByText(/Ollama|Server|URL/i).first()).toBeVisible(); const refreshLlmBtn = window.locator('button:has(svg.lucide-refresh-cw)').first(); if (await refreshLlmBtn.isVisible()) { await refreshLlmBtn.click(); await window.waitForTimeout(400); } // 6. Tab 4: License Tab const licenseTab = window.getByRole('tab', { name: /라이선스|License/i }); await expect(licenseTab).toBeVisible(); await licenseTab.click(); await window.waitForTimeout(500); // Verify Current Tier Display await expect(window.getByText(/현재 라이선스|현재 플랜|FREE|PRO/i).first()).toBeVisible(); // 7. Tab 5: Cloud Sync Tab const cloudTab = window.getByRole('tab', { name: /클라우드|Cloud/i }); if (await cloudTab.isVisible()) { await cloudTab.click(); await window.waitForTimeout(500); await expect(window.getByText(/Cloud|동기화|Sync/i).first()).toBeVisible(); } // 8. Tab 6: About Tab const aboutTab = window.getByRole('tab', { name: /정보|About/i }); if (await aboutTab.isVisible()) { await aboutTab.click(); await window.waitForTimeout(500); await expect(window.getByText(/D3RO Voice|버전|Version/i).first()).toBeVisible(); } // Screenshot SettingsModal await window.screenshot({ path: path.join(SCREENSHOT_DIR, 'rt10_settings_modal_all_tabs.png'), }); // Close SettingsModal const closeBtn = window.locator('div[role="dialog"] button:has(svg.lucide-x)').first(); await expect(closeBtn).toBeVisible(); await closeBtn.click(); await window.waitForTimeout(500); // Verify modal is closed await expect(window.locator('div[role="dialog"]')).not.toBeVisible(); expect(uncaughtExceptions).toEqual([]); }); test('RT-11: HotkeyRecordModal - Interactive Hotkey Recording & Conflict Protection', async () => { // 1. Re-open SettingsModal to General Tab const settingsTrigger = window.getByText('설정', { exact: true }); await settingsTrigger.click(); await window.waitForTimeout(600); // Click General tab const generalTab = window.getByRole('tab', { name: /일반|General/i }); await generalTab.click(); await window.waitForTimeout(400); // 2. Click pencil icon on Dictation shortcut to open HotkeyRecordModal const editHotkeyBtn = window.locator('button:has(svg.lucide-pencil)').first(); await expect(editHotkeyBtn).toBeVisible(); await editHotkeyBtn.click(); await window.waitForTimeout(500); // Verify HotkeyRecordModal is open await expect(window.getByText(/단축키 설정|단축키 녹화|키 조합/i).first()).toBeVisible({ timeout: 5000 }); // 3. Test pressing a key (F9) await window.keyboard.press('F9'); await window.waitForTimeout(400); // Verify chip shows F9 await expect(window.getByText('F9', { exact: true })).toBeVisible(); // Take screenshot while modal is open with captured key await window.screenshot({ path: path.join(SCREENSHOT_DIR, 'rt11_hotkey_modal_verified.png'), }); // 4. Click Cancel button to close HotkeyRecordModal without corrupting bindings const cancelBtn = window.getByRole('button', { name: /취소|Cancel/i }); await expect(cancelBtn).toBeVisible(); await cancelBtn.click(); await window.waitForTimeout(500); // Close SettingsModal const closeSettingsBtn = window.locator('div[role="dialog"] button:has(svg.lucide-x)').first(); if (await closeSettingsBtn.isVisible()) { await closeSettingsBtn.click(); await window.waitForTimeout(500); } expect(uncaughtExceptions).toEqual([]); }); test('RT-12: SupportModal - AI Diagnostics, FAQ Accordion, Refund Query & Telemetry', async () => { // 1. Open SupportModal from sidebar const supportTrigger = window.locator('text=Customer Support').first(); await expect(supportTrigger).toBeVisible({ timeout: 5000 }); await supportTrigger.click(); await window.waitForTimeout(600); // Verify SupportModal opens await expect(window.getByText(/Customer Assistance|고객지원/i).first()).toBeVisible({ timeout: 5000 }); // 2. Tab 0: AI Assistant Chat & Quick Prompt Chips await expect(window.getByText(/AI 어시스턴트|AI Assistant/i).first()).toBeVisible(); // Click quick prompt chip: "🎤 마이크 인식 오류" const micPromptChip = window.getByRole('button', { name: /마이크/i }).first(); if (await micPromptChip.isVisible()) { await micPromptChip.click(); await window.waitForTimeout(800); // Verify AI answer appears await expect(window.getByText(/마이크 입력 진단 결과|개인정보 권한/i)).toBeVisible({ timeout: 5000 }); } // 3. Tab 1: FAQ Accordion const faqTab = window.getByRole('tab', { name: /FAQ|자주 묻는 질문/i }); await expect(faqTab).toBeVisible(); await faqTab.click(); await window.waitForTimeout(500); // Verify FAQ questions render await expect(window.getByText(/마이크 음성 인식이 작동하지 않거나/i)).toBeVisible(); await expect(window.getByText(/NVIDIA CUDA GPU 가속/i)).toBeVisible(); // 4. Tab 3 (Tab index 3 in DOM): 7일 자동 환불 확인 const refundTab = window.getByRole('tab', { name: /환불|Refund/i }); if (await refundTab.isVisible()) { await refundTab.click(); await window.waitForTimeout(500); // Verify refund check button const checkRefundBtn = window.getByRole('button', { name: /환불 자격 조회/i }); if (await checkRefundBtn.isVisible()) { await checkRefundBtn.click(); await window.waitForTimeout(1000); // Verify eligibility result await expect(window.getByText(/전액 환불 가능|조회 완료|환불 자격/i).first()).toBeVisible(); } } // Take screenshot await window.screenshot({ path: path.join(SCREENSHOT_DIR, 'rt12_support_diagnostics_modal.png'), }); // Close SupportModal await window.keyboard.press('Escape'); await window.waitForTimeout(500); expect(uncaughtExceptions).toEqual([]); }); test('RT-13: LicenseModal & Custom Events Fail-Closed Monetization Guardrails', async () => { // 1. Dispatch custom event to open LicenseModal await window.evaluate(() => { window.dispatchEvent(new CustomEvent('d3ro:open-license-modal')); }); await window.waitForTimeout(600); // Verify LicenseModal opens await expect(window.getByText(/라이선스|License/i).first()).toBeVisible({ timeout: 5000 }); await expect(window.getByText(/현재 등급|FREE/i).first()).toBeVisible(); // Verify Tier Comparison Table exists await expect(window.getByText(/등급 비교|Tier Comparison/i)).toBeVisible(); // Take screenshot await window.screenshot({ path: path.join(SCREENSHOT_DIR, 'rt13_license_monetization_modal.png'), }); // Close LicenseModal via Escape await window.keyboard.press('Escape'); await window.waitForTimeout(500); expect(uncaughtExceptions).toEqual([]); }); });