diff --git a/apps/web/e2e/.auth/debug-404-page.png b/apps/web/e2e/.auth/debug-404-page.png deleted file mode 100644 index 2fd650131..000000000 Binary files a/apps/web/e2e/.auth/debug-404-page.png and /dev/null differ diff --git a/apps/web/e2e/.auth/debug-login-page.png b/apps/web/e2e/.auth/debug-login-page.png deleted file mode 100644 index 2fd650131..000000000 Binary files a/apps/web/e2e/.auth/debug-login-page.png and /dev/null differ diff --git a/apps/web/e2e/.auth/user.json b/apps/web/e2e/.auth/user.json deleted file mode 100644 index c51e10921..000000000 --- a/apps/web/e2e/.auth/user.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "cookies": [], - "origins": [ - { - "origin": "http://localhost:5173", - "localStorage": [ - { - "name": "i18nextLng", - "value": "en-US" - } - ] - } - ] -} \ No newline at end of file diff --git a/apps/web/e2e/README.md b/apps/web/e2e/README.md deleted file mode 100644 index cccc3fcfc..000000000 --- a/apps/web/e2e/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# E2E Tests — Parcours critiques et fichiers - -Ce document liste les parcours critiques couverts par les tests E2E Playwright et les fichiers associés. - -## Parcours critiques (Audit 2.10) - -| Parcours | Fichier(s) | Description | -|----------|------------|-------------| -| **Auth** | `tests/auth.spec.ts` | Login, register, logout, route guards, token refresh. Optionnel : 2FA (compte test dédié). | -| **Upload** | `tests/upload.spec.ts` | Upload fichier, upload par chunks. | -| **Purchase** | `tests/purchase.spec.ts` | Marketplace → Add to cart → Checkout → Success. État panier vide. | -| **Chat** | `tests/chat.spec.ts` | Load /chat, UI (Channels, input), état connecté/déconnecté. Envoi message (skip si WebSocket indisponible). | -| **Smoke** | `tests/smoke.spec.ts` | Login → Upload → Création playlist → Ajout track. | -| **Playlists** | `tests/playlists.spec.ts` | Création, liste, modification, ajout/suppression de tracks, suppression playlist, recherche. | -| **Search** | `tests/search.spec.ts` | Navigation vers `/search`, saisie requête, vérification des résultats (tracks/playlists) ou état vide. | -| **Play** | `tests/play.spec.ts` | Après login : search → clic sur un track → page track ou player visible (ou état vide si pas de résultats). | -| **Profile** | `tests/profile.spec.ts` | Affichage profil, informations compte. | -| **Post-deploy smoke** | `tests/smoke-post-deploy.spec.ts` | Health checks (homepage, login, API) against deployed URL. | - -## Post-deploy smoke tests - -Run against a deployed environment (staging/production) without starting the dev server: - -```bash -PLAYWRIGHT_BASE_URL=https://staging.veza.com npx playwright test --config=playwright.config.smoke.ts -``` - -Or with `VITE_FRONTEND_URL`: - -```bash -VITE_FRONTEND_URL=https://app.veza.com npx playwright test --config=playwright.config.smoke.ts -``` - -In CI (cd.yml), the smoke job runs after deploy when `STAGING_URL` (secret or variable) is configured. - -## Prérequis - -- **Frontend** : servi (ex. `npm run dev`) sur l'URL configurée dans `TEST_CONFIG.FRONTEND_URL` (défaut : http://localhost:5173). -- **Backend API** : **obligatoire** pour auth, search, playlists, upload, marketplace (défaut : http://localhost:8080/api/v1). Les tests auth échouent si le backend n'est pas démarré. -- **Chat server** (optionnel) : pour les tests Chat complets (envoi de message). Sans chat server, les tests Chat font du smoke (load UI, état déconnecté). -- **Compte de test** : voir `e2e/utils/test-helpers.ts` : `TEST_USERS.default` (ou `TEST_EMAIL`, `TEST_PASSWORD`). - -**Validation v0.101** : E2E validés uniquement en CI (`.github/workflows/ci.yml`). En local, les credentials Postgres/RabbitMQ peuvent différer (voir `veza-backend-api/.env`). Script d'aide : `./scripts/run-e2e-local.sh` depuis la racine du repo (prérequis : `make infra-up`, backend démarré sur 18080, `veza.fr` dans `/etc/hosts`). - -## Lancer les E2E - -```bash -cd apps/web -npm run test:e2e -# ou -npx playwright test -``` - -Pour un fichier précis : - -```bash -npx playwright test e2e/tests/auth.spec.ts -``` - -Tous les flows critiques (Auth, Upload, Purchase, Chat) : - -```bash -npx playwright test e2e/tests/auth.spec.ts e2e/tests/upload.spec.ts e2e/tests/purchase.spec.ts e2e/tests/chat.spec.ts -``` - -**Machine à ressources limitées** : lancer **un seul spec** à la fois et **un seul projet** (chromium) pour éviter saturation CPU/RAM. Les specs auth, smoke, playlists, search nécessitent que le **Backend API** soit démarré (sinon les appels API échouent en 500). En CI, la suite complète tourne dans le cloud. - -```bash -npx playwright test e2e/tests/auth.spec.ts --project=chromium -``` - -## 2FA E2E - -Le test « should complete login with 2FA code » dans `auth.spec.ts` s'exécute **uniquement** lorsque `E2E_2FA_CODE` est défini. Pour lancer le test 2FA en CI ou en local : - -- **Obligatoire** : `E2E_2FA_CODE` — code TOTP valide au moment de l'exécution (ou code de test si l'env le permet). -- **Optionnel** : `E2E_2FA_EMAIL` — email du compte 2FA (défaut : `TEST_USERS.default.email`). -- **Optionnel** : `E2E_2FA_PASSWORD` — mot de passe du compte (défaut : `TEST_USERS.default.password`). - -Exemple : - -```bash -E2E_2FA_CODE=123456 E2E_2FA_EMAIL=user@example.com E2E_2FA_PASSWORD=secret npx playwright test e2e/tests/auth.spec.ts -g "2FA" -``` diff --git a/apps/web/e2e/crud-operations.spec.ts b/apps/web/e2e/crud-operations.spec.ts deleted file mode 100644 index bdaa18a64..000000000 --- a/apps/web/e2e/crud-operations.spec.ts +++ /dev/null @@ -1,502 +0,0 @@ - -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - openModal, - fillField, - forceSubmitForm, - waitForToast, - setupErrorCapture, -} from './utils/test-helpers'; -import { createMockMP3Buffer } from './fixtures/file-helpers'; - -/** - * CRUD Operations E2E Test Suite - * - * Tests complete CRUD operations for tracks and playlists as specified in INT-TEST-002: - * 1. Track CRUD: Create → Update → Delete - * 2. Playlist CRUD: Create → Add tracks → Delete - * 3. Cleanup test data after execution - * - * This test suite ensures all CRUD operations work end-to-end with a real backend. - */ - -test.describe('CRUD Operations E2E', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - // Store created resources for cleanup - const createdTrackIds: string[] = []; - const createdPlaylistIds: string[] = []; - - // Increase timeout for these tests (uploads can take time) - test.setTimeout(120000); // 2 minutes - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - - // Login before each test - await loginAsUser(page); - await page.waitForTimeout(1000); // Wait for auth to stabilize - }); - - /** - * TEST 1: Complete Track CRUD - * INT-TEST-002: Step 1 - CRUD complet sur tracks - */ - test('should perform complete CRUD operations on tracks', async ({ page }) => { - console.log('🧪 [CRUD] Step 1: Track CRUD - Create'); - - // Navigate to library page - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [CRUD] Timeout on networkidle, continuing...'); - }); - - // CREATE: Upload a new track - await openModal(page, /upload/i); - - // Prepare file - const validMp3Buffer = createMockMP3Buffer(); - const trackTitle = `CRUD Test Track ${Date.now()}`; - const trackArtist = 'Test Artist'; - - // Attach file - const fileInput = page.locator('input[type="file"][accept*="audio"]'); - await fileInput.setInputFiles({ - name: 'crud-test-track.mp3', - mimeType: 'audio/mpeg', - buffer: validMp3Buffer, - }); - - // Fill metadata - await fillField(page, '#title, input[name="title"]', trackTitle); - await fillField(page, '#artist, input[name="artist"]', trackArtist); - - // Handle genre if present - const genreInput = page.locator('#genre, input[name="genre"]').first(); - const isGenreVisible = await genreInput.isVisible().catch(() => false); - if (isGenreVisible) { - await genreInput.fill('Test Genre'); - } - - // Submit form - await forceSubmitForm(page, 'form#upload-track-form, form'); - - // Wait for success - let uploadCompleted = false; - try { - await waitForToast(page, 'success', 10000); - uploadCompleted = true; - console.log('✅ [CRUD] Track created successfully (toast shown)'); - } catch { - // Alternative: wait for modal to close or track to appear in list - await page.waitForTimeout(3000); - const modalClosed = await page.locator('[role="dialog"]').isHidden().catch(() => true); - if (modalClosed) { - uploadCompleted = true; - console.log('✅ [CRUD] Track created successfully (modal closed)'); - } - } - - expect(uploadCompleted).toBe(true); - - // Wait for track to appear in library - await page.waitForTimeout(2000); - - // Verify track appears in library (by title) - const trackInLibrary = page.locator(`text=${trackTitle}`).first(); - await expect(trackInLibrary).toBeVisible({ timeout: 10000 }); - - // Store track ID for cleanup (extract from URL or API response if possible) - const trackUrl = await trackInLibrary.getAttribute('href').catch(() => null); - if (trackUrl) { - const trackIdMatch = trackUrl.match(/\/tracks\/([^/]+)/); - if (trackIdMatch) { - createdTrackIds.push(trackIdMatch[1]); - } - } - - console.log('✅ [CRUD] Step 1 Complete: Track created'); - - // UPDATE: Navigate to track detail page and update metadata - console.log('🧪 [CRUD] Step 2: Track CRUD - Update'); - - if (trackUrl) { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${trackUrl}`); - await page.waitForLoadState('domcontentloaded'); - - // Look for edit button or edit modal - const editButton = page - .locator('button:has-text("Edit"), button:has-text("Modifier"), [aria-label*="edit" i]') - .first(); - - const isEditVisible = await editButton.isVisible({ timeout: 5000 }).catch(() => false); - - if (isEditVisible) { - await editButton.click(); - await page.waitForTimeout(500); - - // Update title - const updatedTitle = `${trackTitle} (Updated)`; - await fillField(page, '#title, input[name="title"]', updatedTitle); - - // Submit update - const saveButton = page - .locator('button:has-text("Save"), button:has-text("Enregistrer"), button[type="submit"]') - .first(); - await saveButton.click(); - - // Wait for success - try { - await waitForToast(page, 'success', 5000); - console.log('✅ [CRUD] Track updated successfully'); - } catch { - // Alternative: wait for page to reload or update - await page.waitForTimeout(2000); - const updatedTitleVisible = await page.locator(`text=${updatedTitle}`).isVisible({ timeout: 5000 }).catch(() => false); - if (updatedTitleVisible) { - console.log('✅ [CRUD] Track updated successfully (title changed)'); - } - } - } else { - console.log('⚠️ [CRUD] Edit button not found, skipping update test'); - } - } else { - console.log('⚠️ [CRUD] Track URL not found, skipping update test'); - } - - console.log('✅ [CRUD] Step 2 Complete: Track updated (if supported)'); - - // DELETE: Delete the track - console.log('🧪 [CRUD] Step 3: Track CRUD - Delete'); - - // Navigate back to library if not already there - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - - // Find the track in the list - const trackItem = page.locator(`text=${trackTitle}`).first(); - await expect(trackItem).toBeVisible({ timeout: 10000 }); - - // Look for delete button (might be in a menu or dropdown) - const deleteButton = page - .locator('button:has-text("Delete"), button:has-text("Supprimer"), [aria-label*="delete" i]') - .first(); - - const isDeleteVisible = await deleteButton.isVisible({ timeout: 5000 }).catch(() => false); - - if (!isDeleteVisible) { - // Try to open a menu/dropdown first - const menuButton = page - .locator('[aria-label*="menu" i], [aria-label*="actions" i], button[aria-haspopup="true"]') - .first(); - const isMenuVisible = await menuButton.isVisible({ timeout: 3000 }).catch(() => false); - - if (isMenuVisible) { - await menuButton.click(); - await page.waitForTimeout(500); - const deleteInMenu = page - .locator('[role="menuitem"]:has-text("Delete"), [role="menuitem"]:has-text("Supprimer")') - .first(); - await deleteInMenu.click(); - } - } else { - await deleteButton.click(); - } - - // Confirm deletion if confirmation dialog appears - const confirmButton = page - .locator('button:has-text("Confirm"), button:has-text("Confirmer"), button:has-text("Delete")') - .first(); - const isConfirmVisible = await confirmButton.isVisible({ timeout: 3000 }).catch(() => false); - - if (isConfirmVisible) { - await confirmButton.click(); - } - - // Wait for success or track to disappear - try { - await waitForToast(page, 'success', 5000); - console.log('✅ [CRUD] Track deleted successfully (toast shown)'); - } catch { - // Alternative: wait for track to disappear from list - await page.waitForTimeout(2000); - const trackStillVisible = await trackItem.isVisible({ timeout: 3000 }).catch(() => true); - if (!trackStillVisible) { - console.log('✅ [CRUD] Track deleted successfully (removed from list)'); - } - } - - console.log('✅ [CRUD] Step 3 Complete: Track deleted'); - }); - - /** - * TEST 2: Complete Playlist CRUD - * INT-TEST-002: Step 2 - CRUD complet sur playlists - */ - test('should perform complete CRUD operations on playlists', async ({ page }) => { - console.log('🧪 [CRUD] Step 1: Playlist CRUD - Create'); - - // Navigate to playlists page - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [CRUD] Timeout on networkidle, continuing...'); - }); - - // CREATE: Create a new playlist - const playlistTitle = `CRUD Test Playlist ${Date.now()}`; - const playlistDescription = 'Test playlist for CRUD operations'; - - await openModal(page, /create|créer|nouvelle/i); - - // Fill playlist form - await fillField(page, '#title, input[name="title"], input[name="name"]', playlistTitle); - - const descriptionInput = page.locator('#description, textarea[name="description"]').first(); - const isDescriptionVisible = await descriptionInput.isVisible({ timeout: 3000 }).catch(() => false); - if (isDescriptionVisible) { - await descriptionInput.fill(playlistDescription); - } - - // Submit form - await forceSubmitForm(page, 'form'); - - // Wait for success - let playlistCreated = false; - try { - await waitForToast(page, 'success', 10000); - playlistCreated = true; - console.log('✅ [CRUD] Playlist created successfully (toast shown)'); - } catch { - // Alternative: wait for modal to close or playlist to appear in list - await page.waitForTimeout(3000); - const modalClosed = await page.locator('[role="dialog"]').isHidden().catch(() => true); - if (modalClosed) { - playlistCreated = true; - console.log('✅ [CRUD] Playlist created successfully (modal closed)'); - } - } - - expect(playlistCreated).toBe(true); - - // Wait for playlist to appear in list - await page.waitForTimeout(2000); - - // Verify playlist appears in list - const playlistInList = page.locator(`text=${playlistTitle}`).first(); - await expect(playlistInList).toBeVisible({ timeout: 10000 }); - - // Store playlist ID for cleanup - const playlistUrl = await playlistInList.getAttribute('href').catch(() => null); - if (playlistUrl) { - const playlistIdMatch = playlistUrl.match(/\/playlists\/([^/]+)/); - if (playlistIdMatch) { - createdPlaylistIds.push(playlistIdMatch[1]); - } - } - - console.log('✅ [CRUD] Step 1 Complete: Playlist created'); - - // ADD TRACKS: Add tracks to the playlist - console.log('🧪 [CRUD] Step 2: Playlist CRUD - Add tracks'); - - if (playlistUrl) { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${playlistUrl}`); - await page.waitForLoadState('domcontentloaded'); - - // Look for "Add tracks" button - const addTracksButton = page - .locator('button:has-text("Add"), button:has-text("Ajouter"), [aria-label*="add" i]') - .first(); - - const isAddTracksVisible = await addTracksButton.isVisible({ timeout: 5000 }).catch(() => false); - - if (isAddTracksVisible) { - await addTracksButton.click(); - await page.waitForTimeout(500); - - // In a real scenario, we would select tracks from a list - // For now, we'll just verify the modal/dialog opens - const addTracksModal = page.locator('[role="dialog"]').first(); - const isModalVisible = await addTracksModal.isVisible({ timeout: 3000 }).catch(() => false); - - if (isModalVisible) { - console.log('✅ [CRUD] Add tracks modal opened'); - - // Close modal (we'll skip actual track selection for now) - const closeButton = page - .locator('button:has-text("Close"), button:has-text("Fermer"), [aria-label*="close" i]') - .first(); - const isCloseVisible = await closeButton.isVisible({ timeout: 3000 }).catch(() => false); - if (isCloseVisible) { - await closeButton.click(); - } else { - // Press Escape - await page.keyboard.press('Escape'); - } - } - } else { - console.log('⚠️ [CRUD] Add tracks button not found, skipping add tracks test'); - } - } else { - console.log('⚠️ [CRUD] Playlist URL not found, skipping add tracks test'); - } - - console.log('✅ [CRUD] Step 2 Complete: Add tracks (if supported)'); - - // DELETE: Delete the playlist - console.log('🧪 [CRUD] Step 3: Playlist CRUD - Delete'); - - // Navigate back to playlists page - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`); - await page.waitForLoadState('domcontentloaded'); - - // Find the playlist in the list - const playlistItem = page.locator(`text=${playlistTitle}`).first(); - await expect(playlistItem).toBeVisible({ timeout: 10000 }); - - // Look for delete button - const deleteButton = page - .locator('button:has-text("Delete"), button:has-text("Supprimer"), [aria-label*="delete" i]') - .first(); - - const isDeleteVisible = await deleteButton.isVisible({ timeout: 5000 }).catch(() => false); - - if (!isDeleteVisible) { - // Try to open a menu/dropdown first - const menuButton = page - .locator('[aria-label*="menu" i], [aria-label*="actions" i], button[aria-haspopup="true"]') - .first(); - const isMenuVisible = await menuButton.isVisible({ timeout: 3000 }).catch(() => false); - - if (isMenuVisible) { - await menuButton.click(); - await page.waitForTimeout(500); - const deleteInMenu = page - .locator('[role="menuitem"]:has-text("Delete"), [role="menuitem"]:has-text("Supprimer")') - .first(); - await deleteInMenu.click(); - } - } else { - await deleteButton.click(); - } - - // Confirm deletion if confirmation dialog appears - const confirmButton = page - .locator('button:has-text("Confirm"), button:has-text("Confirmer"), button:has-text("Delete")') - .first(); - const isConfirmVisible = await confirmButton.isVisible({ timeout: 3000 }).catch(() => false); - - if (isConfirmVisible) { - await confirmButton.click(); - } - - // Wait for success or playlist to disappear - try { - await waitForToast(page, 'success', 5000); - console.log('✅ [CRUD] Playlist deleted successfully (toast shown)'); - } catch { - // Alternative: wait for playlist to disappear from list - await page.waitForTimeout(2000); - const playlistStillVisible = await playlistItem.isVisible({ timeout: 3000 }).catch(() => true); - if (!playlistStillVisible) { - console.log('✅ [CRUD] Playlist deleted successfully (removed from list)'); - } - } - - console.log('✅ [CRUD] Step 3 Complete: Playlist deleted'); - }); - - /** - * CLEANUP: Clean up test data after all tests - * INT-TEST-002: Step 3 - Données de test nettoyées après exécution - */ - test.afterAll(async ({ page }) => { - console.log('\n🧹 [CRUD] Cleaning up test data...'); - - // Login if not already logged in - await loginAsUser(page); - - // Clean up tracks - for (const trackId of createdTrackIds) { - try { - // Navigate to track and delete - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/tracks/${trackId}`); - await page.waitForTimeout(1000); - - const deleteButton = page - .locator('button:has-text("Delete"), button:has-text("Supprimer")') - .first(); - const isVisible = await deleteButton.isVisible({ timeout: 3000 }).catch(() => false); - - if (isVisible) { - await deleteButton.click(); - await page.waitForTimeout(1000); - } - } catch (err) { - console.warn(`⚠️ [CRUD] Failed to cleanup track ${trackId}:`, err); - } - } - - // Clean up playlists - for (const playlistId of createdPlaylistIds) { - try { - // Navigate to playlist and delete - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists/${playlistId}`); - await page.waitForTimeout(1000); - - const deleteButton = page - .locator('button:has-text("Delete"), button:has-text("Supprimer")') - .first(); - const isVisible = await deleteButton.isVisible({ timeout: 3000 }).catch(() => false); - - if (isVisible) { - await deleteButton.click(); - await page.waitForTimeout(1000); - } - } catch (e) { - console.warn(`⚠️ [CRUD] Failed to cleanup playlist ${playlistId}:`, e); - } - } - - console.log('✅ [CRUD] Cleanup complete'); - }); - - /** - * FINAL VERIFICATIONS - */ - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [CRUD] === Final Verifications ==='); - - // Display console errors if present - if (consoleErrors.length > 0) { - console.log(`🔴 [CRUD] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((error) => { - console.log(` - ${error}`); - }); - - if (testInfo.status === 'passed') { - console.warn('⚠️ [CRUD] Test passed but had console errors'); - } - } else { - console.log('✅ [CRUD] No console errors'); - } - - // Display network errors if present - if (networkErrors.length > 0) { - console.log(`🔴 [CRUD] Network errors (${networkErrors.length}):`); - networkErrors.forEach((error) => { - console.log(` - ${error.method} ${error.url}: ${error.status}`); - }); - } else { - console.log('✅ [CRUD] No network errors'); - } - }); -}); - diff --git a/apps/web/e2e/debug-input-focus.spec.ts b/apps/web/e2e/debug-input-focus.spec.ts deleted file mode 100644 index 95687b093..000000000 --- a/apps/web/e2e/debug-input-focus.spec.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * Test de debug pour le problème de focus sur les inputs - * Ce test capture l'état actuel et génère un rapport de debug - * NE REQUIERT PAS d'authentification - */ -test.describe('Debug Input Focus Issue', () => { - test.use({ - // Ne pas utiliser le storageState pour ce test de debug - storageState: undefined, - }); - - test.beforeEach(async ({ page }) => { - // Aller sur la page de login - await page.goto('/login'); - // Attendre que la page soit complètement chargée - await page.waitForLoadState('domcontentloaded'); - await page.waitForTimeout(1000); // Attendre le rendu React - - // Capturer une screenshot pour debug - await page.screenshot({ path: 'test-results/debug-page-loaded.png', fullPage: true }); - - // Vérifier que la page est chargée - const bodyText = await page.textContent('body'); - console.log('📄 Contenu de la page:', bodyText?.substring(0, 200)); - }); - - test('Debug: Vérifier les styles CSS des inputs au chargement', async ({ page }) => { - // Lister tous les inputs pour debug - const allInputs = await page.locator('input').all(); - console.log(`🔍 Nombre d'inputs trouvés: ${allInputs.length}`); - - const inputsInfo = []; - for (let i = 0; i < allInputs.length; i++) { - const input = allInputs[i]; - const type = await input.getAttribute('type') || 'text'; - const name = await input.getAttribute('name') || ''; - const id = await input.getAttribute('id') || ''; - const placeholder = await input.getAttribute('placeholder') || ''; - const classes = await input.getAttribute('class') || ''; - inputsInfo.push({ index: i, type, name, id, placeholder, classes }); - console.log(` Input ${i}: type=${type}, name=${name}, id=${id}, placeholder=${placeholder}`); - } - - // Trouver l'input email (peut être type="email" ou name="email") - let emailInput = page.locator('input[type="email"]').first(); - if (await emailInput.count() === 0) { - emailInput = page.locator('input[name="email"]').first(); - } - if (await emailInput.count() === 0 && allInputs.length > 0) { - // Utiliser le premier input si aucun email spécifique - emailInput = allInputs[0]; - console.log('⚠️ Utilisation du premier input trouvé'); - } - - if (await emailInput.count() === 0) { - throw new Error('Aucun input trouvé sur la page'); - } - - await expect(emailInput).toBeVisible({ timeout: 10000 }); - - // Capturer une screenshot - await page.screenshot({ path: 'test-results/debug-input-initial.png', fullPage: true }); - - // Vérifier les styles CSS appliqués - const emailStyles = await emailInput.evaluate((el) => { - const computed = window.getComputedStyle(el); - return { - borderColor: computed.borderColor, - outline: computed.outline, - outlineWidth: computed.outlineWidth, - boxShadow: computed.boxShadow, - ringWidth: computed.getPropertyValue('--tw-ring-width'), - classes: el.className, - hasFocus: document.activeElement === el, - }; - }); - - console.log('📊 Styles de l\'input Email au chargement:'); - console.log(JSON.stringify(emailStyles, null, 2)); - - // Vérifier qu'il n'y a pas de focus au chargement - expect(emailStyles.hasFocus).toBe(false); - - // Vérifier que le border n'est pas cyan - const borderColorRgb = emailStyles.borderColor; - const hasCyanBorder = borderColorRgb.includes('102') && borderColorRgb.includes('252') && borderColorRgb.includes('241'); - - if (hasCyanBorder) { - console.error('❌ PROBLÈME: Border cyan visible au chargement!'); - console.error(` Border color: ${borderColorRgb}`); - } else { - console.log('✅ Pas de border cyan au chargement'); - } - }); - - test('Debug: Vérifier les styles CSS au clic souris', async ({ page }) => { - // Trouver l'input (peut être type="email" ou name="email" ou premier input) - let emailInput = page.locator('input[type="email"]').first(); - if (await emailInput.count() === 0) { - emailInput = page.locator('input[name="email"]').first(); - } - if (await emailInput.count() === 0) { - emailInput = page.locator('input').first(); - } - await expect(emailInput).toBeVisible({ timeout: 10000 }); - - // Cliquer sur l'input - await emailInput.click(); - await page.waitForTimeout(200); // Attendre que les styles soient appliqués - - // Capturer une screenshot - await page.screenshot({ path: 'test-results/debug-input-after-click.png', fullPage: true }); - - // Vérifier les styles CSS après clic - const emailStyles = await emailInput.evaluate((el) => { - const computed = window.getComputedStyle(el); - return { - borderColor: computed.borderColor, - outline: computed.outline, - outlineWidth: computed.outlineWidth, - boxShadow: computed.boxShadow, - ringWidth: computed.getPropertyValue('--tw-ring-width'), - classes: el.className, - hasFocus: document.activeElement === el, - isFocusVisible: el.matches(':focus-visible'), - }; - }); - - console.log('📊 Styles de l\'input Email après clic:'); - console.log(JSON.stringify(emailStyles, null, 2)); - - // Vérifier qu'il n'y a pas de contour cyan au clic - const borderColorRgb = emailStyles.borderColor; - const hasCyanBorder = borderColorRgb.includes('102') && borderColorRgb.includes('252') && borderColorRgb.includes('241'); - - console.log(`🔍 Border color: ${borderColorRgb}`); - console.log(`🔍 Has cyan border: ${hasCyanBorder}`); - console.log(`🔍 Is focus-visible: ${emailStyles.isFocusVisible}`); - console.log(`🔍 Has focus: ${emailStyles.hasFocus}`); - - // Le border ne devrait PAS être cyan au clic (seulement au clavier) - if (hasCyanBorder && !emailStyles.isFocusVisible) { - console.error('❌ PROBLÈME DÉTECTÉ: Border cyan visible au clic souris!'); - console.error(' Le fix CSS ne fonctionne pas correctement.'); - console.error(` Classes: ${emailStyles.classes}`); - } else if (!hasCyanBorder) { - console.log('✅ Pas de border cyan au clic (correct)'); - } - }); - - test('Debug: Vérifier les styles CSS au clavier (Tab)', async ({ page }) => { - // Trouver l'input (peut être type="email" ou name="email" ou premier input) - let emailInput = page.locator('input[type="email"]').first(); - if (await emailInput.count() === 0) { - emailInput = page.locator('input[name="email"]').first(); - } - if (await emailInput.count() === 0) { - emailInput = page.locator('input').first(); - } - await expect(emailInput).toBeVisible({ timeout: 10000 }); - - // Naviguer avec Tab - await page.keyboard.press('Tab'); - await page.waitForTimeout(200); - - // Capturer une screenshot - await page.screenshot({ path: 'test-results/debug-input-after-tab.png', fullPage: true }); - - // Vérifier les styles CSS après Tab - const emailStyles = await emailInput.evaluate((el) => { - const computed = window.getComputedStyle(el); - return { - borderColor: computed.borderColor, - outline: computed.outline, - outlineWidth: computed.outlineWidth, - boxShadow: computed.boxShadow, - ringWidth: computed.getPropertyValue('--tw-ring-width'), - classes: el.className, - hasFocus: document.activeElement === el, - isFocusVisible: el.matches(':focus-visible'), - }; - }); - - console.log('📊 Styles de l\'input Email après Tab:'); - console.log(JSON.stringify(emailStyles, null, 2)); - - // Au clavier, le border devrait être cyan (mais discret) - const borderColorRgb = emailStyles.borderColor; - const hasCyanBorder = borderColorRgb.includes('102') && borderColorRgb.includes('252') && borderColorRgb.includes('241'); - - console.log(`🔍 Border color: ${borderColorRgb}`); - console.log(`🔍 Has cyan border: ${hasCyanBorder}`); - console.log(`🔍 Is focus-visible: ${emailStyles.isFocusVisible}`); - - // Au clavier, le border devrait être cyan - if (emailStyles.isFocusVisible && !hasCyanBorder) { - console.warn('⚠️ Le border cyan n\'apparaît pas au clavier (focus-visible)'); - } else if (emailStyles.isFocusVisible && hasCyanBorder) { - console.log('✅ Border cyan visible au clavier (correct)'); - } - }); - - test('Debug: Analyser toutes les classes CSS appliquées', async ({ page }) => { - // Trouver l'input (peut être type="email" ou name="email" ou premier input) - let emailInput = page.locator('input[type="email"]').first(); - if (await emailInput.count() === 0) { - emailInput = page.locator('input[name="email"]').first(); - } - if (await emailInput.count() === 0) { - emailInput = page.locator('input').first(); - } - await expect(emailInput).toBeVisible({ timeout: 10000 }); - - // Analyser toutes les classes et styles - const analysis = await emailInput.evaluate((el) => { - const computed = window.getComputedStyle(el); - const allStyles: Record = {}; - - // Récupérer tous les styles CSS - for (let i = 0; i < computed.length; i++) { - const prop = computed[i]; - allStyles[prop] = computed.getPropertyValue(prop); - } - - return { - classes: el.className, - classList: Array.from(el.classList), - hasFocusClass: el.className.includes('focus:'), - hasFocusVisibleClass: el.className.includes('focus-visible:'), - inlineStyle: el.getAttribute('style'), - computedStyles: { - borderColor: computed.borderColor, - borderWidth: computed.borderWidth, - borderStyle: computed.borderStyle, - outline: computed.outline, - outlineWidth: computed.outlineWidth, - boxShadow: computed.boxShadow, - '--tw-ring-width': computed.getPropertyValue('--tw-ring-width'), - '--tw-ring-color': computed.getPropertyValue('--tw-ring-color'), - }, - allStyles: Object.fromEntries( - Object.entries(allStyles).filter(([key]) => - key.includes('border') || - key.includes('outline') || - key.includes('ring') || - key.includes('shadow') - ) - ), - }; - }); - - console.log('📊 Analyse complète de l\'input Email:'); - console.log(JSON.stringify(analysis, null, 2)); - - // Vérifier si les classes problématiques sont présentes - if (analysis.hasFocusClass) { - console.warn('⚠️ Classes focus: détectées dans className:', analysis.classList.filter(c => c.includes('focus:'))); - } - }); - - test('Debug: Vérifier que le fix CSS est chargé', async ({ page }) => { - // Vérifier que le fichier fix-input-focus.css est chargé - const stylesheets = await page.evaluate(() => { - return Array.from(document.styleSheets).map((sheet, index) => { - try { - return { - index, - href: sheet.href || 'inline', - rules: sheet.cssRules ? Array.from(sheet.cssRules).length : 0, - }; - } catch (e) { - return { - index, - href: sheet.href || 'inline', - rules: 'cross-origin', - }; - } - }); - }); - - console.log('📊 Feuilles de style chargées:'); - console.log(JSON.stringify(stylesheets, null, 2)); - - // Vérifier que fix-input-focus.css est présent - const hasFixCss = stylesheets.some(s => s.href && s.href.includes('fix-input-focus')); - console.log(`🔍 Fix CSS chargé: ${hasFixCss}`); - - // Vérifier les règles CSS pour input:focus - const focusRules = await page.evaluate(() => { - const rules: Array<{ selector: string; borderColor?: string }> = []; - Array.from(document.styleSheets).forEach((sheet) => { - try { - if (sheet.cssRules) { - Array.from(sheet.cssRules).forEach((rule: any) => { - if (rule.selectorText && rule.selectorText.includes('input') && rule.selectorText.includes('focus')) { - const style = rule.style; - rules.push({ - selector: rule.selectorText, - borderColor: style.borderColor || style.getPropertyValue('border-color'), - }); - } - }); - } - } catch (e) { - // Cross-origin stylesheet, ignorer - } - }); - return rules; - }); - - console.log('📊 Règles CSS pour input:focus trouvées:'); - console.log(JSON.stringify(focusRules, null, 2)); - }); -}); diff --git a/apps/web/e2e/e2e/diagnostic-login-page.png b/apps/web/e2e/e2e/diagnostic-login-page.png deleted file mode 100644 index a85aa2f99..000000000 Binary files a/apps/web/e2e/e2e/diagnostic-login-page.png and /dev/null differ diff --git a/apps/web/e2e/error-boundary.spec.ts b/apps/web/e2e/error-boundary.spec.ts deleted file mode 100644 index 38bf3123f..000000000 --- a/apps/web/e2e/error-boundary.spec.ts +++ /dev/null @@ -1,335 +0,0 @@ - -import { test, expect } from '@playwright/test'; -import { TEST_CONFIG } from './utils/test-helpers'; - -/** - * Error Boundary Tests - * - * These tests verify that error boundaries work correctly and handle errors gracefully. - * Tests cover: - * - Error boundary display when errors occur - * - Error recovery (retry functionality) - * - Navigation from error state - * - Error boundary in different contexts (pages, components) - * - * To run error boundary tests: - * - Run: npx playwright test error-boundary - */ - -test.describe('Error Boundary Tests', () => { - // Use authenticated state for most tests - test.use({ storageState: 'e2e/.auth/user.json' }); - - test.describe('Error Boundary Display', () => { - test('should display error boundary UI when error occurs', async ({ page }) => { - // Navigate to a page that might trigger an error - // We'll simulate an error by navigating to an invalid route or triggering an error - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Inject an error into the page to trigger error boundary - await page.evaluate(() => { - // Simulate a React error by throwing in a component - // eslint-disable-next-line no-undef - const errorEvent = new ErrorEvent('error', { - message: 'Test error for error boundary', - error: new Error('Test error'), - }); - window.dispatchEvent(errorEvent); - }); - - // Wait a bit for error boundary to catch - await page.waitForTimeout(1000); - - // Check if error boundary UI is displayed - // Error boundary should show error message or fallback UI - const errorText = page.locator('text=/erreur|error|Oups/i').first(); - await expect(errorText.count()).resolves.toBeGreaterThanOrEqual(0); - - // Error boundary might not always trigger from injected errors, - // but we can check if the page is still functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - - test('should handle JavaScript errors gracefully', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Listen for console errors - const consoleErrors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - consoleErrors.push(msg.text()); - } - }); - - // Trigger a JavaScript error - await page.evaluate(() => { - try { - // Access undefined property to trigger error - - (window as any).nonExistentFunction(); - } catch { - // Error caught, but should be handled by error boundary if in React tree - } - }); - - // Page should still be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - }); - - test.describe('Error Recovery', () => { - test('should have retry button in error boundary', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Look for retry button (error boundary might not be visible, but button should exist if error occurs) - const retryButton = page.locator('button:has-text("Réessayer"), button:has-text("Retry"), button:has-text("réessayer")').first(); - - // If error boundary is visible, retry button should be there - await expect(retryButton.count()).resolves.toBeGreaterThanOrEqual(0); - - // At minimum, page should be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - - test('should allow navigation from error state', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Look for home button or navigation link - const homeButton = page.locator('button:has-text("Accueil"), button:has-text("Home"), a[href="/"]').first(); - - // If error boundary is visible, home button should allow navigation - if (await homeButton.count() > 0) { - await homeButton.click({ timeout: 5000 }); - // Should navigate away from error state - await page.waitForTimeout(1000); - } - - // Page should still be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - }); - - test.describe('Network Error Handling', () => { - test('should handle API errors gracefully', async ({ page }) => { - // Intercept API requests and return errors - await page.route('**/api/**', (route) => { - route.fulfill({ - status: 500, - contentType: 'application/json', - body: JSON.stringify({ error: 'Internal Server Error' }), - }); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Page should still render, even with API errors - const body = page.locator('body'); - await expect(body).toBeVisible(); - - // Error messages might be displayed, but page should not crash - // Error messages might be displayed, but page should not crash - await expect(page.locator('text=/erreur|error/i').first().count()).resolves.toBeGreaterThanOrEqual(0); - }); - - test('should handle 404 errors gracefully', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-page-12345`); - await page.waitForLoadState('networkidle'); - - // Should show 404 page or error message, not blank page - const body = page.locator('body'); - const bodyText = await body.textContent(); - - expect(bodyText).not.toBe(''); - expect(bodyText).not.toBeNull(); - - // Should have some error or 404 message - const errorMessage = page.locator('text=/404|not found|introuvable|erreur/i').first(); - const hasErrorMessage = await errorMessage.count() > 0; - - // Either error message or navigation should be available - expect(hasErrorMessage || true).toBe(true); - }); - - test('should handle timeout errors', async ({ page }) => { - // Intercept API requests and delay them to cause timeout - await page.route('**/api/**', (route) => { - // Don't fulfill, let it timeout - setTimeout(() => { - route.continue(); - }, 10000); // Long delay - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - - // Wait for page to load (might timeout, but should handle gracefully) - try { - await page.waitForLoadState('networkidle', { timeout: 5000 }); - } catch { - // Timeout expected, but page should still be functional - } - - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - }); - - test.describe('Component Error Handling', () => { - test('should handle component render errors', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Try to interact with components that might error - const buttons = page.locator('button').first(); - if (await buttons.count() > 0) { - // Click might trigger errors in some components - try { - await buttons.click({ timeout: 2000 }); - } catch { - // Error might occur, but should be handled - } - } - - // Page should still be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - - test('should handle form submission errors', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('networkidle'); - - // Try to submit form with invalid data - const submitButton = page.locator('button[type="submit"]').first(); - if (await submitButton.count() > 0) { - try { - await submitButton.click({ timeout: 2000 }); - await page.waitForTimeout(1000); - } catch { - // Error might occur, but should be handled - } - } - - // Page should still be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - }); - - test.describe('Error Boundary UI Elements', () => { - test('should display error icon or indicator', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Look for error indicators (icons, alerts, etc.) - const errorIcon = page.locator('[aria-label*="error"], [aria-label*="erreur"], svg[class*="error"]').first(); - - // Error icon might not be visible if no error occurred - // But if error boundary is shown, icon should be there - await expect(errorIcon.count()).resolves.toBeGreaterThanOrEqual(0); - - // At minimum, page should be visible - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - - test('should display helpful error message', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Look for error messages - const errorMessages = [ - 'erreur', - 'error', - 'Oups', - 'Une erreur', - 'Something went wrong', - ]; - - const foundMessage = false; - for (const message of errorMessages) { - const locator = page.locator(`text=/${message}/i`).first(); - if (await locator.count() > 0) { - break; - } - } - - // Error message might not be visible if no error occurred - // But page should still be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - }); - - test.describe('Error Boundary Integration', () => { - test('should work with React Router navigation', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Navigate to different pages - const profileLink = page.locator('a[href="/profile"], a[href*="profile"]').first(); - if (await profileLink.count() > 0) { - await profileLink.click({ timeout: 5000 }); - await page.waitForURL('**/profile', { timeout: 5000 }); - } - - // Navigate back - await page.goBack(); - await page.waitForTimeout(1000); - - // Page should still be functional after navigation - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - - test('should preserve error state during navigation', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Navigate to another page - const profileLink = page.locator('a[href="/profile"], a[href*="profile"]').first(); - if (await profileLink.count() > 0) { - await profileLink.click({ timeout: 5000 }); - await page.waitForURL('**/profile', { timeout: 5000 }); - } - - // Page should be functional - const body = page.locator('body'); - await expect(body).toBeVisible(); - }); - }); - - test.describe('Error Logging', () => { - test('should log errors to console', async ({ page }) => { - const consoleErrors: string[] = []; - - page.on('console', (msg) => { - if (msg.type() === 'error') { - consoleErrors.push(msg.text()); - } - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Trigger an error - await page.evaluate(() => { - console.error('Test error for logging'); - }); - - await page.waitForTimeout(500); - - // Errors should be logged (at least our test error) - expect(consoleErrors.length).toBeGreaterThanOrEqual(0); - }); - }); -}); - diff --git a/apps/web/e2e/error-handling.spec.ts b/apps/web/e2e/error-handling.spec.ts deleted file mode 100644 index 115920049..000000000 --- a/apps/web/e2e/error-handling.spec.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - setupErrorCapture, - waitForToast, - fillField, - forceSubmitForm, -} from './utils/test-helpers'; - -/** - * Error Handling E2E Test Suite - * - * Tests error handling throughout the application: - * - Network errors (offline, timeout, 500) - * - Validation errors (form validation) - * - API errors (400, 401, 403, 404, 500) - * - Error boundaries (React error boundaries) - * - User-friendly error messages - * - Error recovery - */ - -test.describe('Error Handling', () => { - - - test.beforeEach(async ({ page }) => { - setupErrorCapture(page); - }); - - test.describe('Network Errors', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - }); - - test('should handle offline mode gracefully', async ({ page }) => { - // Go offline - await page.context().setOffline(true); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - - // Should show offline message or cached content - const offlineIndicator = page.locator('text=offline, text=No internet, text=Connection lost').first(); - const cachedContent = page.locator('[data-testid="tracks-list"], [data-testid="library"]').first(); - - const hasOfflineMessage = await offlineIndicator.isVisible({ timeout: 3000 }).catch(() => false); - const hasCachedContent = await cachedContent.isVisible({ timeout: 3000 }).catch(() => false); - - expect(hasOfflineMessage || hasCachedContent).toBeTruthy(); - - // Go back online - await page.context().setOffline(false); - }); - - test('should handle API timeout errors', async ({ page }) => { - // Intercept API calls and delay them to simulate timeout - await page.route('**/api/v1/tracks**', async (route) => { - await new Promise(resolve => setTimeout(resolve, 10000)); // 10 second delay - route.abort('timedout'); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should show timeout error or loading state - const timeoutError = await waitForToast(page, 'error', 15000).catch(() => null); - const loadingState = page.locator('text=Loading, [data-testid="loading"]').first(); - - expect(timeoutError !== null || await loadingState.isVisible({ timeout: 2000 }).catch(() => false)).toBeTruthy(); - }); - - test('should handle 500 server errors', async ({ page }) => { - // Intercept API calls and return 500 - await page.route('**/api/v1/tracks**', (route) => { - route.fulfill({ - status: 500, - contentType: 'application/json', - body: JSON.stringify({ error: 'Internal Server Error' }), - }); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should show error message - const errorToast = await waitForToast(page, 'error', 5000).catch(() => null); - expect(errorToast).toBeTruthy(); - }); - - test('should handle 503 service unavailable', async ({ page }) => { - await page.route('**/api/v1/tracks**', (route) => { - route.fulfill({ - status: 503, - contentType: 'application/json', - body: JSON.stringify({ error: 'Service Unavailable' }), - }); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - const errorToast = await waitForToast(page, 'error', 5000).catch(() => null); - expect(errorToast).toBeTruthy(); - }); - }); - - test.describe('Authentication Errors', () => { - test('should handle 401 unauthorized errors', async ({ page }) => { - // Start unauthenticated - test.use({ storageState: { cookies: [], origins: [] } }); - - // Try to access protected route - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should redirect to login - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/(login|auth/login)`)); - }); - - test('should handle invalid login credentials', async ({ page }) => { - test.use({ storageState: { cookies: [], origins: [] } }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - // Fill form with invalid credentials - await fillField(page, 'input[type="email"]', 'invalid@example.com'); - await fillField(page, 'input[type="password"]', 'wrongpassword'); - - const loginForm = page.locator('form').first(); - await forceSubmitForm(page, loginForm); - - // Should show error message - const errorToast = await waitForToast(page, 'error', 5000).catch(() => null); - const errorMessage = page.locator('text=Invalid, text=incorrect, text=wrong').first(); - - expect(errorToast !== null || await errorMessage.isVisible({ timeout: 3000 }).catch(() => false)).toBeTruthy(); - }); - - test('should handle expired token gracefully', async ({ page }) => { - await loginAsUser(page); - - // Simulate expired token by clearing it - await page.evaluate(() => { - localStorage.clear(); - sessionStorage.clear(); - }); - - // Try to access protected route - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should redirect to login or show error - const currentUrl = page.url(); - const redirectedToLogin = currentUrl.includes('/login'); - const errorShown = await waitForToast(page, 'error', 3000).catch(() => null); - - expect(redirectedToLogin || errorShown !== null).toBeTruthy(); - }); - }); - - test.describe('Validation Errors', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - }); - - test('should show validation errors for empty required fields', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('networkidle'); - - // Try to submit empty form - const registerForm = page.locator('form').first(); - if (await registerForm.isVisible({ timeout: 2000 }).catch(() => false)) { - await forceSubmitForm(page, registerForm); - - // Should show validation errors - const emailError = page.locator('text=required, text=email').first(); - const passwordError = page.locator('text=required, text=password').first(); - - const hasEmailError = await emailError.isVisible({ timeout: 2000 }).catch(() => false); - const hasPasswordError = await passwordError.isVisible({ timeout: 2000 }).catch(() => false); - - expect(hasEmailError || hasPasswordError).toBeTruthy(); - } - }); - - test('should show validation error for invalid email format', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('networkidle'); - - const emailInput = page.locator('input[type="email"]').first(); - if (await emailInput.isVisible({ timeout: 2000 }).catch(() => false)) { - await fillField(page, 'input[type="email"]', 'invalid-email'); - - // Blur to trigger validation - await emailInput.blur(); - - // Should show validation error - const emailError = page.locator('text=invalid, text=email format').first(); - const hasError = await emailError.isVisible({ timeout: 2000 }).catch(() => false); - - // HTML5 validation might also show browser tooltip - const isValid = await emailInput.evaluate((el: HTMLInputElement) => el.validity.valid); - expect(hasError || !isValid).toBeTruthy(); - } - }); - - test('should show validation error for password mismatch', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('networkidle'); - - const passwordInput = page.locator('input[type="password"]').first(); - const confirmPasswordInput = page.locator('input[name*="confirm"], input[name*="passwordConfirm"]').first(); - - if (await passwordInput.isVisible({ timeout: 2000 }).catch(() => false) && - await confirmPasswordInput.isVisible({ timeout: 2000 }).catch(() => false)) { - await fillField(page, 'input[type="password"]', 'password123'); - await fillField(page, 'input[name*="confirm"], input[name*="passwordConfirm"]', 'different123'); - - // Blur to trigger validation - await confirmPasswordInput.blur(); - - // Should show validation error - const passwordError = page.locator('text=match, text=password, text=do not match').first(); - const hasError = await passwordError.isVisible({ timeout: 2000 }).catch(() => false); - expect(hasError).toBeTruthy(); - } - }); - }); - - test.describe('API Error Responses', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - }); - - test('should handle 400 bad request errors', async ({ page }) => { - await page.route('**/api/v1/tracks**', (route) => { - route.fulfill({ - status: 400, - contentType: 'application/json', - body: JSON.stringify({ - success: false, - error: { - code: 'VALIDATION_ERROR', - message: 'Invalid request data' - } - }), - }); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - const errorToast = await waitForToast(page, 'error', 5000).catch(() => null); - expect(errorToast).toBeTruthy(); - }); - - test('should handle 403 forbidden errors', async ({ page }) => { - await page.route('**/api/v1/tracks/*/delete**', (route) => { - route.fulfill({ - status: 403, - contentType: 'application/json', - body: JSON.stringify({ - success: false, - error: { - code: 'FORBIDDEN', - message: 'You do not have permission to perform this action' - } - }), - }); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Try to delete a track (if delete button exists) - const deleteButton = page.locator('button[aria-label*="delete"], button[title*="delete"]').first(); - if (await deleteButton.isVisible({ timeout: 2000 }).catch(() => false)) { - await deleteButton.click(); - - const errorToast = await waitForToast(page, 'error', 5000).catch(() => null); - expect(errorToast).toBeTruthy(); - } - }); - - test('should handle 404 not found errors', async ({ page }) => { - await page.route('**/api/v1/tracks/non-existent-id**', (route) => { - route.fulfill({ - status: 404, - contentType: 'application/json', - body: JSON.stringify({ - success: false, - error: { - code: 'NOT_FOUND', - message: 'Track not found' - } - }), - }); - }); - - // Try to access non-existent track - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/tracks/non-existent-id`); - await page.waitForLoadState('networkidle'); - - // Should show 404 message or redirect - const notFoundMessage = page.locator('text=404, text=Not Found, text=not found').first(); - const errorToast = await waitForToast(page, 'error', 3000).catch(() => null); - - expect(await notFoundMessage.isVisible({ timeout: 2000 }).catch(() => false) || errorToast !== null).toBeTruthy(); - }); - }); - - test.describe('Error Recovery', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - }); - - test('should allow retry after network error', async ({ page }) => { - let requestCount = 0; - - await page.route('**/api/v1/tracks**', (route) => { - requestCount++; - if (requestCount === 1) { - // First request fails - route.abort('failed'); - } else { - // Subsequent requests succeed - route.continue(); - } - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should show error - const errorToast = await waitForToast(page, 'error', 5000).catch(() => null); - - // Look for retry button - const retryButton = page.locator('button:has-text("Retry"), button:has-text("Try again")').first(); - if (await retryButton.isVisible({ timeout: 2000 }).catch(() => false)) { - await retryButton.click(); - - // Should retry and succeed - await page.waitForTimeout(2000); - expect(requestCount).toBeGreaterThan(1); - } else { - // Retry might be automatic or not implemented - expect(errorToast !== null || requestCount > 1).toBeTruthy(); - } - }); - - test('should clear errors when navigating away', async ({ page }) => { - // Trigger an error - await page.route('**/api/v1/tracks**', (route) => { - route.fulfill({ - status: 500, - contentType: 'application/json', - body: JSON.stringify({ error: 'Server Error' }), - }); - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Error should be shown - await waitForToast(page, 'error', 5000).catch(() => null); - - // Navigate away - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Error toast should be gone (or dismissed) - await page.waitForTimeout(1000); - // This is hard to test directly, but navigation should work - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/(dashboard)?`)); - }); - }); -}); - diff --git a/apps/web/e2e/fixtures/file-helpers.ts b/apps/web/e2e/fixtures/file-helpers.ts deleted file mode 100644 index cf4c4da6b..000000000 --- a/apps/web/e2e/fixtures/file-helpers.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { writeFileSync } from 'fs'; - -/** - * Crée un fichier MP3 simulé pour les tests - * Utilise un buffer MP3 valide (frame MP3 avec silence) pour que le backend - * puisse extraire les métadonnées (durée, etc.) sans bloquer - */ -export function createMockMP3File(filePath: string): void { - // Petit buffer représentant une frame MP3 valide (silence) - // Ce buffer contient des headers MP3 valides et des métadonnées ID3 - // qui permettront au backend d'extraire les informations nécessaires - const validMp3Buffer = Buffer.from( - '//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD//OEAAAAAAAAAAAAAAAAAAAAAAAATGF2YzU4LjU0AAAAAAAAAAAAAAAAJAAAAAAAAAAAASAAAAAAAASAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAALAAA', - 'base64', - ); - - writeFileSync(filePath, validMp3Buffer); -} - -/** - * Crée un buffer MP3 valide pour les tests d'upload - * Utilisé avec setInputFiles() dans Playwright - */ -export function createMockMP3Buffer(): Buffer { - // Buffer MP3 valide minimal (Header ID3 + Frame Silence) - return Buffer.from( - '4944330300000000000a544954320000000500000054657374fffb90440000000000000000000000000000000000000000', - 'hex', - ); -} - -/** - * Crée un fichier MP3 plus volumineux pour tester le chunked upload - * @param filePath - Chemin où créer le fichier - * @param sizeInMB - Taille du fichier en MB (défaut: 15 MB) - */ -export function createLargeMockMP3File(filePath: string, sizeInMB: number = 15): void { - const sizeInBytes = sizeInMB * 1024 * 1024; - const baseBuffer = createMockMP3Buffer(); - - // Répéter le buffer pour atteindre la taille désirée - const chunks = Math.ceil(sizeInBytes / baseBuffer.length); - const buffers: Buffer[] = []; - - for (let i = 0; i < chunks; i++) { - buffers.push(baseBuffer); - } - - const largeBuffer = Buffer.concat(buffers).slice(0, sizeInBytes); - writeFileSync(filePath, largeBuffer); -} - -/** - * Crée un buffer MP3 large pour les tests d'upload chunké (in-memory) - * Utilisé avec setInputFiles() dans Playwright pour les gros fichiers - * - * @param sizeInMB - Taille du fichier en MB (défaut: 15 MB) - * @returns Buffer - Buffer MP3 valide de la taille spécifiée - * - * @example - * const largeBuffer = createLargeMockMP3Buffer(20); // 20 MB - * await fileInput.setInputFiles({ - * name: 'large-track.mp3', - * mimeType: 'audio/mpeg', - * buffer: largeBuffer, - * }); - */ -export function createLargeMockMP3Buffer(sizeInMB: number = 15): Buffer { - const sizeInBytes = sizeInMB * 1024 * 1024; - const baseBuffer = createMockMP3Buffer(); - - // Répéter le buffer pour atteindre la taille désirée - const chunks = Math.ceil(sizeInBytes / baseBuffer.length); - const buffers: Buffer[] = []; - - for (let i = 0; i < chunks; i++) { - buffers.push(baseBuffer); - } - - const largeBuffer = Buffer.concat(buffers).slice(0, sizeInBytes); - return largeBuffer; -} - -/** - * Formats de fichiers audio supportés pour les tests - */ -export const SUPPORTED_AUDIO_FORMATS = { - mp3: { - mimeType: 'audio/mpeg', - extension: '.mp3', - }, - flac: { - mimeType: 'audio/flac', - extension: '.flac', - }, - wav: { - mimeType: 'audio/wav', - extension: '.wav', - }, - ogg: { - mimeType: 'audio/ogg', - extension: '.ogg', - }, - m4a: { - mimeType: 'audio/mp4', - extension: '.m4a', - }, - aac: { - mimeType: 'audio/aac', - extension: '.aac', - }, -} as const; diff --git a/apps/web/e2e/global-setup.ts b/apps/web/e2e/global-setup.ts deleted file mode 100644 index ccbba047f..000000000 --- a/apps/web/e2e/global-setup.ts +++ /dev/null @@ -1,216 +0,0 @@ - -import * as fs from 'fs'; -import * as path from 'path'; -import { chromium, FullConfig } from '@playwright/test'; -import { TEST_CONFIG } from './utils/test-helpers'; - -// Load test user credentials from environment or use defaults -const getTestUser = () => { - const email = process.env.TEST_EMAIL || 'e2e@test.com'; - const password = process.env.TEST_PASSWORD || 'Xk9$mP2#vL7@nQ4!wR8'; - return { email, password }; -}; - -/** - * Global Setup for Playwright E2E Tests - * - * This setup runs ONCE before all tests to: - * 1. Log in as a test user - * 2. Save the authenticated session state to storageState.json - * 3. All subsequent tests will use this saved state (no need to login again) - * - * This eliminates: - * - Rate limiting issues (only 1 login instead of N logins) - * - Test execution time (no login overhead per test) - * - Flaky authentication failures - */ - -async function globalSetup(config: FullConfig) { - console.log('🔧 [GLOBAL SETUP] Starting global setup...'); - - const testUser = getTestUser(); - console.log(`🔧 [GLOBAL SETUP] Using test user: ${testUser.email}`); - - // Use the first project's browser (usually chromium) - // Use the first project's browser (usually chromium) - const browser = await chromium.launch({ - headless: true, - }); - - const context = await browser.newContext(); - const page = await context.newPage(); - - try { - // Step 1: Navigate to frontend first (required for relative API URLs - fetch needs a base URL) - console.log('🔧 [GLOBAL SETUP] Navigating to frontend...'); - await page.goto(TEST_CONFIG.FRONTEND_URL, { - waitUntil: 'domcontentloaded', - timeout: 30000, - }); - - // Step 2: Verify API is available (page has base URL for relative fetch) - console.log('🔧 [GLOBAL SETUP] Verifying API availability...'); - console.log(`🔧 [GLOBAL SETUP] API URL: ${TEST_CONFIG.API_URL}`); - - const healthCheckResult = await page.evaluate(async ({ apiUrl }) => { - try { - // When apiUrl is relative (e.g. /api/v1), health is at /api/v1/health (proxy forwards /api) - const healthUrl = apiUrl.startsWith('/') - ? `${apiUrl.replace(/\/$/, '')}/health` - : `${apiUrl.replace(/\/api\/v1\/?$/, '')}/api/v1/health`; - console.log(`[BROWSER] Health check: ${healthUrl}`); - const healthResponse = await fetch(healthUrl, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - signal: AbortSignal.timeout(10000), // 10s timeout - }); - return { success: healthResponse.ok, status: healthResponse.status }; - } catch (error) { - return { success: false, error: error instanceof Error ? error.message : String(error) }; - } - }, { apiUrl: TEST_CONFIG.API_URL }); - - if (!healthCheckResult.success) { - console.warn(`⚠️ [GLOBAL SETUP] API health check failed: ${healthCheckResult.error || `Status ${healthCheckResult.status}`}`); - console.warn(`⚠️ [GLOBAL SETUP] Continuing anyway - API might be starting up...`); - } else { - console.log('✅ [GLOBAL SETUP] API is available'); - } - - // Login via API directly in the browser context - console.log('🔧 [GLOBAL SETUP] Attempting API login via browser...'); - const loginResult = await page.evaluate(async ({ apiUrl, email, password }) => { - try { - console.log(`[BROWSER] Attempting login to: ${apiUrl}/auth/login`); - - const loginAttempt = async () => { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout - - const response = await fetch(`${apiUrl}/auth/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email, - password, - }), - signal: controller.signal, - }); - clearTimeout(timeoutId); - return response; - }; - - let response = await loginAttempt(); - - // If login fails with 401, attempt to register the user - if (response.status === 401) { - console.warn(`[BROWSER] Login failed with 401. Attempting to register user: ${email}`); - const registerResponse = await fetch(`${apiUrl}/auth/register`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email, - password, - password_confirmation: password, // Required by backend DTO - username: email.split('@')[0], // Use email prefix as username first_name: 'E2E', - last_name: 'Test', - terms_accepted: true, - }), }); - - if (!registerResponse.ok) { - const errorText = await registerResponse.text(); - console.error(`[BROWSER] Registration failed: HTTP ${registerResponse.status}: ${errorText}`); - return { success: false, error: `Registration failed: HTTP ${registerResponse.status}: ${errorText}` }; - } - console.log(`[BROWSER] User ${email} registered successfully. Attempting login again.`); - response = await loginAttempt(); // Try logging in again after registration - } - - if (!response.ok) { - const errorText = await response.text(); - return { success: false, error: `HTTP ${response.status}: ${errorText}` }; - } - - const data = await response.json(); - const accessToken = data?.token?.access_token || data?.data?.token?.access_token || data?.access_token; - const refreshToken = data?.token?.refresh_token || data?.data?.token?.refresh_token || data?.refresh_token; - - if (!accessToken) { - return { success: false, error: 'No access token in response', data }; - } - - // Store tokens in localStorage - localStorage.setItem('veza_access_token', accessToken); - if (refreshToken) { - localStorage.setItem('veza_refresh_token', refreshToken); - } - - // Also set auth-storage for Zustand - const authStorage = { - state: { - isAuthenticated: true, - accessToken, - refreshToken, - }, - }; - localStorage.setItem('auth-storage', JSON.stringify(authStorage)); - - return { success: true, accessToken, refreshToken }; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(`[BROWSER] Login error: ${errorMessage}`); - // Check if it's a network error - if (errorMessage.includes('Failed to fetch') || errorMessage.includes('NetworkError') || errorMessage.includes('aborted')) { - return { success: false, error: `Network error: ${errorMessage}. Is the API running at ${apiUrl}?` }; - } - return { success: false, error: errorMessage }; - } - }, { apiUrl: TEST_CONFIG.API_URL, email: testUser.email, password: testUser.password }); - - if (!loginResult.success) { - const errorMsg = loginResult.error || 'Unknown error'; - console.warn(`⚠️ [GLOBAL SETUP] API login failed: ${errorMsg}`); - console.warn(`⚠️ [GLOBAL SETUP] Make sure Backend API is running at ${TEST_CONFIG.API_URL} and test user exists: ${testUser.email}`); - // Write empty storage state so Playwright can start; specs that need auth use their own login or storageState override - const storageStatePath = config.projects[0]?.use?.storageState as string || 'e2e/.auth/user.json'; - fs.mkdirSync(path.dirname(storageStatePath), { recursive: true }); - await context.storageState({ path: storageStatePath }); - console.warn(`⚠️ [GLOBAL SETUP] Saved empty auth state to ${storageStatePath}. Tests requiring API will fail until backend is running.`); - await browser.close(); - return; - } - - console.log('✅ [GLOBAL SETUP] API login successful!'); - console.log(`✅ [GLOBAL SETUP] Access token: ${loginResult.accessToken?.substring(0, 20)}...`); - - // Verify tokens are stored - const storedToken = await page.evaluate(() => localStorage.getItem('veza_access_token')); - if (!storedToken) { - throw new Error('Token not stored in localStorage'); - } - - // Save the authenticated state - const storageStatePath = config.projects[0]?.use?.storageState as string || 'e2e/.auth/user.json'; - console.log(`💾 [GLOBAL SETUP] Saving authenticated state to: ${storageStatePath}`); - await context.storageState({ path: storageStatePath }); - - console.log('✅ [GLOBAL SETUP] Global setup completed successfully!'); - } catch (error) { - console.error('❌ [GLOBAL SETUP] Global setup failed:', error); - throw error; - } finally { - await browser.close(); - } -} - -export default globalSetup; - - - - - - diff --git a/apps/web/e2e/mobile-responsive.spec.ts-snapshots/dashboard-iphone12-chromium-linux.png b/apps/web/e2e/mobile-responsive.spec.ts-snapshots/dashboard-iphone12-chromium-linux.png deleted file mode 100644 index 57b9fb290..000000000 Binary files a/apps/web/e2e/mobile-responsive.spec.ts-snapshots/dashboard-iphone12-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/navigation.spec.ts b/apps/web/e2e/navigation.spec.ts deleted file mode 100644 index 5a07added..000000000 --- a/apps/web/e2e/navigation.spec.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - setupErrorCapture, - navigateViaHref, -} from './utils/test-helpers'; - -/** - * Navigation E2E Test Suite - * - * Tests the complete navigation flow of the application: - * - Sidebar navigation - * - Route guards (protected routes) - * - Deep linking - * - Browser back/forward navigation - * - Active route highlighting - * - Mobile navigation (responsive) - */ - -test.describe('Navigation Flow', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - test.describe('Authenticated Navigation', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - }); - - test('should navigate to dashboard from sidebar', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Click dashboard link in sidebar - const dashboardLink = page.locator('nav a[href="/dashboard"], nav a[href="/"]').first(); - await expect(dashboardLink).toBeVisible(); - await dashboardLink.click(); - - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/?(dashboard)?$`)); - }); - - test('should navigate to library from sidebar', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const libraryLink = page.locator('nav a[href="/library"]').first(); - await expect(libraryLink).toBeVisible(); - await libraryLink.click(); - - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/library`)); - }); - - test('should navigate to playlists from sidebar', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const playlistsLink = page.locator('nav a[href="/playlists"]').first(); - await expect(playlistsLink).toBeVisible(); - await playlistsLink.click(); - - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/playlists`)); - }); - - test('should navigate to profile from sidebar', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Profile link might be in a dropdown menu - const profileLink = page.locator('nav a[href*="/profile"], nav a[href*="/user"]').first(); - if (await profileLink.isVisible({ timeout: 2000 }).catch(() => false)) { - await profileLink.click(); - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/(profile|user)`)); - } else { - // Try clicking avatar/user menu first - const userMenu = page.locator('button[aria-label*="user"], button[aria-label*="menu"], [data-testid="user-menu"]').first(); - if (await userMenu.isVisible({ timeout: 2000 }).catch(() => false)) { - await userMenu.click(); - const profileLinkInMenu = page.locator('a[href*="/profile"], a[href*="/user"]').first(); - await expect(profileLinkInMenu).toBeVisible({ timeout: 5000 }); - await profileLinkInMenu.click(); - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/(profile|user)`)); - } - } - }); - - test('should highlight active route in sidebar', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Check if library link has active state - const libraryLink = page.locator('nav a[href="/library"]').first(); - const isActive = await libraryLink.evaluate((el) => { - return el.classList.contains('active') || - el.getAttribute('aria-current') === 'page' || - el.closest('[aria-current="page"]') !== null; - }); - - // Some apps use different active indicators, so we just check it's visible - await expect(libraryLink).toBeVisible(); - }); - - test('should support browser back navigation', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Navigate to library - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/library`)); - - // Go back - await page.goBack(); - await page.waitForLoadState('networkidle'); - - // Should be back on dashboard (or previous page) - const currentUrl = page.url(); - expect(currentUrl).toMatch(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/(dashboard|library)?`)); - }); - - test('should support browser forward navigation', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Navigate to library - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Go back - await page.goBack(); - await page.waitForLoadState('networkidle'); - - // Go forward - await page.goForward(); - await page.waitForLoadState('networkidle'); - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/library`)); - }); - - test('should support deep linking to protected routes', async ({ page }) => { - // Direct navigation to a protected route - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should be able to access the route (already authenticated) - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/library`)); - - // Page should be loaded (not showing login) - const loginForm = page.locator('form[action*="login"], input[type="email"]'); - await expect(loginForm).not.toBeVisible({ timeout: 2000 }); - }); - }); - - test.describe('Unauthenticated Navigation', () => { - // Reset storage state to ensure we're not authenticated - test.use({ storageState: { cookies: [], origins: [] } }); - - test('should redirect to login when accessing protected route', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('networkidle'); - - // Should redirect to login - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/(login|auth/login)`)); - }); - - test('should allow access to public routes', async ({ page }) => { - // Try to access login page - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - // Should be on login page - const loginForm = page.locator('form[action*="login"], input[type="email"]').first(); - await expect(loginForm).toBeVisible({ timeout: 5000 }); - }); - - test('should allow access to register page', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('networkidle'); - - // Should be on register page - const registerForm = page.locator('form[action*="register"], input[name*="email"]').first(); - await expect(registerForm).toBeVisible({ timeout: 5000 }); - }); - }); - - test.describe('Mobile Navigation', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - // Set mobile viewport - await page.setViewportSize({ width: 375, height: 667 }); - }); - - test('should show mobile menu when hamburger is clicked', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Look for hamburger menu button - const hamburgerButton = page.locator('button[aria-label*="menu"], button[aria-label*="navigation"], [data-testid="mobile-menu-button"]').first(); - - if (await hamburgerButton.isVisible({ timeout: 2000 }).catch(() => false)) { - await hamburgerButton.click(); - - // Menu should be visible - const mobileMenu = page.locator('nav[aria-label*="mobile"], nav[data-testid="mobile-nav"]').first(); - await expect(mobileMenu).toBeVisible({ timeout: 3000 }); - } else { - // Mobile menu might not be implemented, skip test - test.skip(); - } - }); - - test('should navigate from mobile menu', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const hamburgerButton = page.locator('button[aria-label*="menu"], button[aria-label*="navigation"]').first(); - - if (await hamburgerButton.isVisible({ timeout: 2000 }).catch(() => false)) { - await hamburgerButton.click(); - - // Click library link in mobile menu - const libraryLink = page.locator('nav a[href="/library"]').first(); - await expect(libraryLink).toBeVisible({ timeout: 3000 }); - await libraryLink.click(); - - await expect(page).toHaveURL(new RegExp(`${TEST_CONFIG.FRONTEND_URL}/library`)); - } else { - test.skip(); - } - }); - }); - - test.describe('Error Handling', () => { - test.beforeEach(async ({ page }) => { - await loginAsUser(page); - }); - - test('should handle 404 pages gracefully', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-page-12345`); - await page.waitForLoadState('networkidle'); - - // Should show 404 page or redirect to dashboard - const currentUrl = page.url(); - const has404Content = await page.locator('text=404, text=Not Found, text=Page not found').first().isVisible({ timeout: 2000 }).catch(() => false); - const redirectedToDashboard = currentUrl.includes('/dashboard') || currentUrl === `${TEST_CONFIG.FRONTEND_URL }/`; - - expect(has404Content || redirectedToDashboard).toBeTruthy(); - }); - - test('should handle navigation errors gracefully', async ({ page }) => { - // Intercept navigation and simulate error - await page.route('**/api/**', (route) => { - if (route.request().url().includes('/library')) { - route.abort('failed'); - } else { - route.continue(); - } - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Try to navigate to library (should handle error) - const libraryLink = page.locator('nav a[href="/library"]').first(); - if (await libraryLink.isVisible({ timeout: 2000 }).catch(() => false)) { - await libraryLink.click(); - - // Should show error message or stay on current page - await page.waitForTimeout(2000); - const errorToast = page.locator('text=error, text=Error, text=failed').first(); - const stillOnDashboard = page.url().includes('/dashboard'); - - // Either error is shown or we're still on dashboard - expect(await errorToast.isVisible({ timeout: 2000 }).catch(() => false) || stillOnDashboard).toBeTruthy(); - } - }); - }); -}); - diff --git a/apps/web/e2e/performance.spec.ts b/apps/web/e2e/performance.spec.ts deleted file mode 100644 index 1ee25d647..000000000 --- a/apps/web/e2e/performance.spec.ts +++ /dev/null @@ -1,669 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { TEST_CONFIG } from './utils/test-helpers'; - -/** - * Performance Tests - * - * These tests measure page load times, render performance, and Core Web Vitals. - * Performance metrics are captured using Playwright's performance API and - * browser Performance Timing API. - * - * To run only performance tests: - * - Run: npx playwright test performance - * - * Performance thresholds: - * - Page load time: < 3 seconds - * - First Contentful Paint (FCP): < 1.8 seconds - * - Largest Contentful Paint (LCP): < 2.5 seconds - * - Time to Interactive (TTI): < 3.8 seconds - * - Total Blocking Time (TBT): < 300ms - */ - -interface PerformanceMetrics { - loadTime: number; - domContentLoaded: number; - firstPaint: number; - firstContentfulPaint: number; - largestContentfulPaint: number; - timeToInteractive: number; - totalBlockingTime: number; - cumulativeLayoutShift: number; - firstInputDelay: number; - networkRequests: number; - jsHeapSizeUsed: number; -} - -/** - * Capture performance metrics from the browser - */ -async function capturePerformanceMetrics(page: any): Promise { - return await page.evaluate(() => { - const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; - const paint = performance.getEntriesByType('paint'); - const measure = performance.getEntriesByType('measure'); - - // Calculate load time - const loadTime = navigation.loadEventEnd - navigation.fetchStart; - const domContentLoaded = navigation.domContentLoadedEventEnd - navigation.fetchStart; - - // Get paint metrics - const firstPaint = paint.find((entry) => entry.name === 'first-paint')?.startTime || 0; - const firstContentfulPaint = paint.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0; - - // Get LCP (Largest Contentful Paint) - approximate using load event - const largestContentfulPaint = navigation.loadEventEnd - navigation.fetchStart; - - // Calculate TTI (Time to Interactive) - approximate - const timeToInteractive = navigation.domInteractive - navigation.fetchStart; - - // Calculate TBT (Total Blocking Time) - approximate - // This is a simplified calculation - const totalBlockingTime = Math.max(0, navigation.domInteractive - navigation.domContentLoadedEventEnd); - - // Get CLS (Cumulative Layout Shift) - requires PerformanceObserver - let cumulativeLayoutShift = 0; - if ('PerformanceObserver' in window) { - try { - const clsEntries: any[] = []; - const observer = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - if (!(entry as any).hadRecentInput) { - clsEntries.push(entry); - } - } - }); - observer.observe({ type: 'layout-shift', buffered: true }); - cumulativeLayoutShift = clsEntries.reduce((sum, entry: any) => sum + entry.value, 0); - } catch (e) { - // CLS not supported - } - } - - // Get FID (First Input Delay) - approximate - const firstInputDelay = 0; // Would need PerformanceObserver for real measurement - - // Count network requests - const networkRequests = performance.getEntriesByType('resource').length; - - // Get memory usage (if available) - const memory = (performance as any).memory; - const jsHeapSizeUsed = memory ? memory.usedJSHeapSize : 0; - - return { - loadTime, - domContentLoaded, - firstPaint, - firstContentfulPaint, - largestContentfulPaint, - timeToInteractive, - totalBlockingTime, - cumulativeLayoutShift, - firstInputDelay, - networkRequests, - jsHeapSizeUsed, - }; - }); -} - -/** - * Wait for page to be fully loaded and stable - */ -async function waitForPageStable(page: any, timeout = 10000) { - await page.waitForLoadState('networkidle', { timeout }); - await page.waitForLoadState('domcontentloaded'); - // Wait a bit more for any async operations - await page.waitForTimeout(1000); -} - -test.describe('Performance Tests', () => { - // Use authenticated state for most tests - test.use({ storageState: 'e2e/.auth/user.json' }); - - test.describe('Page Load Performance', () => { - test('dashboard page load time should be acceptable', async ({ page }) => { - const startTime = Date.now(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await waitForPageStable(page); - - const endTime = Date.now(); - const loadTime = endTime - startTime; - - const metrics = await capturePerformanceMetrics(page); - - // Log metrics for debugging - console.log('Dashboard Performance Metrics:', { - loadTime: `${loadTime}ms`, - domContentLoaded: `${metrics.domContentLoaded.toFixed(2)}ms`, - firstContentfulPaint: `${metrics.firstContentfulPaint.toFixed(2)}ms`, - largestContentfulPaint: `${metrics.largestContentfulPaint.toFixed(2)}ms`, - timeToInteractive: `${metrics.timeToInteractive.toFixed(2)}ms`, - networkRequests: metrics.networkRequests, - }); - - // Assertions - thresholds based on Core Web Vitals - expect(loadTime).toBeLessThan(5000); // 5 seconds max - expect(metrics.domContentLoaded).toBeLessThan(3000); // 3 seconds - expect(metrics.firstContentfulPaint).toBeLessThan(1800); // 1.8 seconds (Good FCP) - expect(metrics.largestContentfulPaint).toBeLessThan(2500); // 2.5 seconds (Good LCP) - }); - - test('login page load time should be fast', async ({ page }) => { - // Use unauthenticated state for login page - await page.context().clearCookies(); - - const startTime = Date.now(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await waitForPageStable(page); - - const endTime = Date.now(); - const loadTime = endTime - startTime; - - const metrics = await capturePerformanceMetrics(page); - - console.log('Login Page Performance Metrics:', { - loadTime: `${loadTime}ms`, - firstContentfulPaint: `${metrics.firstContentfulPaint.toFixed(2)}ms`, - networkRequests: metrics.networkRequests, - }); - - // Login page should be very fast (no data loading) - expect(loadTime).toBeLessThan(2000); // 2 seconds max - expect(metrics.firstContentfulPaint).toBeLessThan(1000); // 1 second - }); - - test('profile page load time should be acceptable', async ({ page }) => { - const startTime = Date.now(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await waitForPageStable(page); - - const endTime = Date.now(); - const loadTime = endTime - startTime; - - const metrics = await capturePerformanceMetrics(page); - - expect(loadTime).toBeLessThan(5000); - expect(metrics.firstContentfulPaint).toBeLessThan(1800); - }); - - test('tracks page load time should be acceptable', async ({ page }) => { - const startTime = Date.now(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/tracks`); - await waitForPageStable(page); - - const endTime = Date.now(); - const loadTime = endTime - startTime; - - const metrics = await capturePerformanceMetrics(page); - - expect(loadTime).toBeLessThan(5000); - expect(metrics.firstContentfulPaint).toBeLessThan(1800); - }); - - test('playlists page load time should be acceptable', async ({ page }) => { - const startTime = Date.now(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`); - await waitForPageStable(page); - - const endTime = Date.now(); - const loadTime = endTime - startTime; - - const metrics = await capturePerformanceMetrics(page); - - expect(loadTime).toBeLessThan(5000); - expect(metrics.firstContentfulPaint).toBeLessThan(1800); - }); - }); - - test.describe('Render Performance', () => { - test('dashboard should render main content quickly', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - - // Measure time to render main content - const renderStart = Date.now(); - await page.waitForSelector('main, [role="main"]', { timeout: 10000 }); - const renderEnd = Date.now(); - const renderTime = renderEnd - renderStart; - - console.log(`Dashboard main content render time: ${renderTime}ms`); - - expect(renderTime).toBeLessThan(2000); // Should render in under 2 seconds - }); - - test('navigation should be responsive', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await waitForPageStable(page); - - // Measure navigation time - const navStart = Date.now(); - await page.click('a[href="/profile"]', { timeout: 5000 }); - await page.waitForURL('**/profile', { timeout: 5000 }); - await waitForPageStable(page); - const navEnd = Date.now(); - const navTime = navEnd - navStart; - - console.log(`Navigation time (dashboard -> profile): ${navTime}ms`); - - expect(navTime).toBeLessThan(3000); // Navigation should be fast - }); - }); - - test.describe('Network Performance', () => { - test('should minimize network requests on initial load', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await waitForPageStable(page); - - const metrics = await capturePerformanceMetrics(page); - - console.log(`Total network requests: ${metrics.networkRequests}`); - - // Should not have excessive network requests - // This threshold may need adjustment based on actual usage - expect(metrics.networkRequests).toBeLessThan(50); - }); - - test('API requests should complete quickly', async ({ page }) => { - const requestTimes: number[] = []; - - // Track API request times - page.on('response', (response: any) => { - const url = response.url(); - if (url.includes('/api/')) { - const timing = response.timing(); - if (timing) { - const requestTime = timing.responseEnd - timing.requestStart; - requestTimes.push(requestTime); - } - } - }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await waitForPageStable(page); - - if (requestTimes.length > 0) { - const avgRequestTime = requestTimes.reduce((a, b) => a + b, 0) / requestTimes.length; - const maxRequestTime = Math.max(...requestTimes); - - console.log(`Average API request time: ${avgRequestTime.toFixed(2)}ms`); - console.log(`Max API request time: ${maxRequestTime.toFixed(2)}ms`); - - // API requests should complete reasonably quickly - expect(avgRequestTime).toBeLessThan(1000); // Average under 1 second - expect(maxRequestTime).toBeLessThan(3000); // Max under 3 seconds - } - }); - }); - - test.describe('Memory Performance', () => { - test('should not have excessive memory usage', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await waitForPageStable(page); - - const metrics = await capturePerformanceMetrics(page); - - if (metrics.jsHeapSizeUsed > 0) { - const heapSizeMB = metrics.jsHeapSizeUsed / (1024 * 1024); - console.log(`JS Heap Size Used: ${heapSizeMB.toFixed(2)}MB`); - - // Should not use excessive memory (threshold: 100MB) - expect(heapSizeMB).toBeLessThan(100); - } - }); - }); - - test.describe('Large Dataset Performance', () => { - test('should render large track lists (1000+ tracks) smoothly', async ({ page }) => { - // Mock a large track list with 1000+ tracks - const largeTrackList = Array.from({ length: 1200 }, (_, i) => ({ - id: `track-${i + 1}`, - title: `Track ${i + 1}`, - artist: `Artist ${Math.floor(i / 10) + 1}`, - duration: 180 + (i % 60), // Varying durations - file_path: `/tracks/track-${i + 1}.mp3`, - file_size: 5000000 + (i * 1000), - format: 'mp3', - is_public: true, - play_count: Math.floor(Math.random() * 1000), - like_count: Math.floor(Math.random() * 100), - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - creator_id: 'test-user', - status: 'ready' as const, - })); - - // Intercept tracks API call and return mocked data - await page.route('**/api/v1/tracks**', async (route) => { - if (route.request().method() === 'GET') { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - success: true, - data: largeTrackList, - total: largeTrackList.length, - page: 1, - limit: largeTrackList.length, - }), - }); - } else { - await route.continue(); - } - }); - - // Navigate to library page - const renderStart = Date.now(); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - - // Wait for library content to be visible - await page.waitForSelector('[data-testid="library-page"], .library-page, main', { timeout: 10000 }); - - // Wait for tracks to be rendered (check for virtualized list or track items) - await page.waitForSelector( - '[data-testid="track-list"], .track-list, [role="list"], table, [role="table"], [data-testid="virtualized-list"]', - { timeout: 10000 } - ).catch(() => { - // If specific selector not found, wait for any content - console.warn('⚠️ [PERF] Specific track list selector not found, waiting for general content'); - }); - - const renderEnd = Date.now(); - const renderTime = renderEnd - renderStart; - - // Measure performance metrics - const metrics = await capturePerformanceMetrics(page); - - // Count rendered track items (virtualization may only render visible items) - const trackCount = await page.evaluate(() => { - const selectors = [ - '[data-testid*="track"]', - '[data-track-id]', - '[role="listitem"]', - 'tr[data-track-id]', - '.track-item', - 'li', - ]; - let count = 0; - for (const selector of selectors) { - const elements = document.querySelectorAll(selector); - if (elements.length > 0) { - count = elements.length; - break; - } - } - return count; - }); - - // Check if virtualization is working (should render fewer items than total) - const isVirtualized = trackCount < largeTrackList.length; - - console.log('Large Track List Performance Metrics:', { - renderTime: `${renderTime}ms`, - totalTracks: `${largeTrackList.length} tracks`, - renderedTracks: `${trackCount} tracks rendered`, - isVirtualized: isVirtualized ? 'Yes' : 'No', - domContentLoaded: `${metrics.domContentLoaded.toFixed(2)}ms`, - firstContentfulPaint: `${metrics.firstContentfulPaint.toFixed(2)}ms`, - largestContentfulPaint: `${metrics.largestContentfulPaint.toFixed(2)}ms`, - timeToInteractive: `${metrics.timeToInteractive.toFixed(2)}ms`, - networkRequests: metrics.networkRequests, - }); - - // Verify performance thresholds - // Large track lists should render in reasonable time (8 seconds max for 1000+ tracks) - expect(renderTime).toBeLessThan(8000); - - // Verify that tracks are being rendered (at least some tracks should be visible) - expect(trackCount).toBeGreaterThan(0); - - // Verify smooth rendering - LCP should be acceptable for large lists - expect(metrics.largestContentfulPaint).toBeLessThan(4000); // 4 seconds for very large lists - - // Verify virtualization is working (should not render all 1000+ tracks at once) - if (isVirtualized) { - console.log('✅ [PERF] Virtualization detected - only visible tracks rendered'); - } else { - console.warn('⚠️ [PERF] Virtualization may not be working - all tracks may be rendered'); - } - - console.log('✅ [PERF] Large track list rendered smoothly'); - }); - - test('should render large playlists (100+ tracks) smoothly', async ({ page }) => { - // Mock a playlist with 100+ tracks - const largePlaylist = { - id: 'test-large-playlist', - name: 'Large Playlist Test', - description: 'Performance test with 100+ tracks', - tracks: Array.from({ length: 120 }, (_, i) => ({ - id: `track-${i + 1}`, - title: `Track ${i + 1}`, - artist: `Artist ${i + 1}`, - duration: 180 + (i % 60), // Varying durations - file_path: `/tracks/track-${i + 1}.mp3`, - file_size: 5000000 + (i * 1000), - format: 'mp3', - is_public: true, - play_count: Math.floor(Math.random() * 1000), - like_count: Math.floor(Math.random() * 100), - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - creator_id: 'test-user', - })), - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - creator_id: 'test-user', - }; - - // Intercept playlist API call and return mocked data - await page.route('**/api/v1/playlists/**', async (route) => { - if (route.request().method() === 'GET') { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - success: true, - data: largePlaylist, - }), - }); - } else { - await route.continue(); - } - }); - - // Navigate to playlist page - const renderStart = Date.now(); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists/${largePlaylist.id}`); - - // Wait for playlist content to be visible - await page.waitForSelector('[data-testid="playlist-detail"], .playlist-detail, main', { timeout: 10000 }); - - // Wait for tracks to be rendered (check for track list or items) - await page.waitForSelector( - '[data-testid="playlist-tracks"], .playlist-tracks, [role="list"], table, [role="table"]', - { timeout: 10000 } - ).catch(() => { - // If specific selector not found, wait for any content - console.warn('⚠️ [PERF] Specific track list selector not found, waiting for general content'); - }); - - const renderEnd = Date.now(); - const renderTime = renderEnd - renderStart; - - // Measure performance metrics - const metrics = await capturePerformanceMetrics(page); - - // Count rendered track items - const trackCount = await page.evaluate(() => { - const selectors = [ - '[data-testid*="track"]', - '[role="listitem"]', - 'tr[data-track-id]', - '.track-item', - 'li', - ]; - let count = 0; - for (const selector of selectors) { - const elements = document.querySelectorAll(selector); - if (elements.length > 0) { - count = elements.length; - break; - } - } - return count; - }); - - console.log('Large Playlist Performance Metrics:', { - renderTime: `${renderTime}ms`, - trackCount: `${trackCount} tracks rendered`, - domContentLoaded: `${metrics.domContentLoaded.toFixed(2)}ms`, - firstContentfulPaint: `${metrics.firstContentfulPaint.toFixed(2)}ms`, - largestContentfulPaint: `${metrics.largestContentfulPaint.toFixed(2)}ms`, - timeToInteractive: `${metrics.timeToInteractive.toFixed(2)}ms`, - networkRequests: metrics.networkRequests, - }); - - // Verify performance thresholds - // Large playlists should render in reasonable time (5 seconds max for 100+ tracks) - expect(renderTime).toBeLessThan(5000); - - // Verify that tracks are being rendered (at least some tracks should be visible) - // Note: Virtualization might only render visible tracks, so we check for > 0 - expect(trackCount).toBeGreaterThan(0); - - // Verify smooth rendering - LCP should be acceptable - expect(metrics.largestContentfulPaint).toBeLessThan(3000); // 3 seconds for large lists - - console.log('✅ [PERF] Large playlist rendered smoothly'); - }); - - test('should render many conversations (100+) smoothly', async ({ page }) => { - // Mock a large conversation list with 100+ conversations - const largeConversationList = Array.from({ length: 120 }, (_, i) => ({ - id: `conversation-${i + 1}`, - name: `Conversation ${i + 1}`, - type: i % 3 === 0 ? 'direct' : 'channel', - participants: i % 3 === 0 ? [`user-${i}`, `user-${i + 1}`] : [], - unread_count: i % 5 === 0 ? Math.floor(Math.random() * 10) : 0, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - })); - - // Intercept conversations API call and return mocked data - await page.route('**/api/v1/conversations**', async (route) => { - if (route.request().method() === 'GET') { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - success: true, - conversations: largeConversationList, - }), - }); - } else { - await route.continue(); - } - }); - - // Navigate to chat page - const renderStart = Date.now(); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/chat`); - - // Wait for chat content to be visible - await page.waitForSelector('[data-testid="chat-page"], .chat-page, main, [data-testid="chat-sidebar"]', { timeout: 10000 }); - - // Wait for conversations to be rendered (check for conversation list or items) - await page.waitForSelector( - '[data-testid="conversation-list"], .conversation-list, [role="list"], [data-testid*="conversation"]', - { timeout: 10000 } - ).catch(() => { - // If specific selector not found, wait for any content - console.warn('⚠️ [PERF] Specific conversation list selector not found, waiting for general content'); - }); - - const renderEnd = Date.now(); - const renderTime = renderEnd - renderStart; - - // Measure performance metrics - const metrics = await capturePerformanceMetrics(page); - - // Count rendered conversation items - const conversationCount = await page.evaluate(() => { - const selectors = [ - '[data-testid*="conversation"]', - '[data-conversation-id]', - '[role="listitem"]', - '.conversation-item', - 'li', - ]; - let count = 0; - for (const selector of selectors) { - const elements = document.querySelectorAll(selector); - if (elements.length > 0) { - count = elements.length; - break; - } - } - return count; - }); - - console.log('Many Conversations Performance Metrics:', { - renderTime: `${renderTime}ms`, - totalConversations: `${largeConversationList.length} conversations`, - renderedConversations: `${conversationCount} conversations rendered`, - domContentLoaded: `${metrics.domContentLoaded.toFixed(2)}ms`, - firstContentfulPaint: `${metrics.firstContentfulPaint.toFixed(2)}ms`, - largestContentfulPaint: `${metrics.largestContentfulPaint.toFixed(2)}ms`, - timeToInteractive: `${metrics.timeToInteractive.toFixed(2)}ms`, - networkRequests: metrics.networkRequests, - }); - - // Verify performance thresholds - // Many conversations should render in reasonable time (5 seconds max for 100+ conversations) - expect(renderTime).toBeLessThan(5000); - - // Verify that conversations are being rendered (at least some conversations should be visible) - expect(conversationCount).toBeGreaterThan(0); - - // Verify smooth rendering - LCP should be acceptable - expect(metrics.largestContentfulPaint).toBeLessThan(3000); // 3 seconds for large lists - - console.log('✅ [PERF] Many conversations rendered smoothly'); - }); - }); - - test.describe('Core Web Vitals', () => { - test('should meet Core Web Vitals thresholds', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await waitForPageStable(page); - - const metrics = await capturePerformanceMetrics(page); - - // Core Web Vitals thresholds (Good) - const coreWebVitals = { - LCP: metrics.largestContentfulPaint, // Should be < 2.5s - FID: metrics.firstInputDelay, // Should be < 100ms (not measured here) - CLS: metrics.cumulativeLayoutShift, // Should be < 0.1 - FCP: metrics.firstContentfulPaint, // Should be < 1.8s - TBT: metrics.totalBlockingTime, // Should be < 300ms - }; - - console.log('Core Web Vitals:', { - LCP: `${coreWebVitals.LCP.toFixed(2)}ms (target: < 2500ms)`, - FCP: `${coreWebVitals.FCP.toFixed(2)}ms (target: < 1800ms)`, - TBT: `${coreWebVitals.TBT.toFixed(2)}ms (target: < 300ms)`, - CLS: `${coreWebVitals.CLS.toFixed(4)} (target: < 0.1)`, - }); - - // Assert Core Web Vitals thresholds - expect(coreWebVitals.LCP).toBeLessThan(2500); - expect(coreWebVitals.FCP).toBeLessThan(1800); - expect(coreWebVitals.TBT).toBeLessThan(300); - expect(coreWebVitals.CLS).toBeLessThan(0.1); - }); - }); -}); - diff --git a/apps/web/e2e/playwright-report-visual/index.html b/apps/web/e2e/playwright-report-visual/index.html deleted file mode 100644 index 051c896e5..000000000 --- a/apps/web/e2e/playwright-report-visual/index.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - Playwright Test Report - - - - -
- - - \ No newline at end of file diff --git a/apps/web/e2e/setup-test-user-role.sh b/apps/web/e2e/setup-test-user-role.sh deleted file mode 100755 index e85bdb445..000000000 --- a/apps/web/e2e/setup-test-user-role.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/bin/bash -# Script pour promouvoir l'utilisateur de test en "artist" -# Usage: ./setup-test-user-role.sh - -set -e - -# Configuration par défaut (peut être surchargée par variables d'environnement) -# Valeurs par défaut basées sur docker-compose.yml du projet -DB_HOST="${DB_HOST:-localhost}" -DB_PORT="${DB_PORT:-5432}" -DB_NAME="${DB_NAME:-veza}" -DB_USER="${DB_USER:-veza}" -DB_PASSWORD="${DB_PASSWORD:-password}" -TEST_USER_EMAIL="${TEST_USER_EMAIL:-user@example.com}" -POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-veza_postgres}" - -echo "🔧 [SETUP] Promoting test user to 'artist' role..." -echo " User: $TEST_USER_EMAIL" -echo " Database: $DB_NAME@$DB_HOST:$DB_PORT" - -# Option 1: Utiliser psql directement -if command -v psql &> /dev/null; then - echo "📝 [SETUP] Using psql..." - PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" < /dev/null; then - echo "🐳 [SETUP] Using Docker exec..." - - # Chercher le conteneur PostgreSQL - if docker ps --format "{{.Names}}" | grep -q "^${POSTGRES_CONTAINER}$"; then - CONTAINER_NAME="$POSTGRES_CONTAINER" - elif docker ps --format "{{.Names}}" | grep -qi postgres; then - CONTAINER_NAME=$(docker ps --format "{{.Names}}" | grep -i postgres | head -n 1) - else - echo "❌ [SETUP] No PostgreSQL container found" - echo " Tried: $POSTGRES_CONTAINER" - echo " Available containers:" - docker ps --format "{{.Names}}" || echo " (none running)" - exit 1 - fi - - echo " Using container: $CONTAINER_NAME" - - docker exec -i "$CONTAINER_NAME" psql -U "$DB_USER" -d "$DB_NAME" < { - // Reset storage state for these tests to ensure we start unauthenticated - test.use({ storageState: { cookies: [], origins: [] } }); - - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - /** - * TEST 1: Login avec credentials valides - */ - test('should login successfully with valid credentials', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que le formulaire soit prêt (premier test peut être plus lent) - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { }); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('input[type="email"], input[name="email"]').first()).toBeVisible({ timeout: 5000 }); - await page.waitForTimeout(500); - - // Remplir le formulaire - await fillField( - page, - 'input[type="email"], input[name="email"]', - TEST_USERS.default.email - ); - await fillField(page, 'input[type="password"], input[name="password"]', TEST_USERS.default.password); - - // Soumettre le formulaire - const navigationPromise = page.waitForURL( - (url) => url.pathname === '/dashboard' || url.pathname === '/', - { timeout: 15000 } - ); - - await forceSubmitForm(page, 'form'); - await navigationPromise; - - // Vérifier que l'utilisateur est redirigé et authentifié - await expect(page).toHaveURL(/\/(dashboard|$)/); - await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({ - timeout: 10000, - }); - - // Wait for Zustand to persist auth-storage (async) - await page.waitForTimeout(1000); - - // Vérifier l'état d'authentification (accepte les tokens en mémoire) - const token = await getAuthToken(page); - expect(token).toBeTruthy(); // Peut être un token réel ou "memory-token" - - // Vérifier aussi que isAuthenticated est true dans le storage - const isAuthenticated = await page.evaluate(() => { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } - } catch { - return false; - } - return false; - }); - expect(isAuthenticated).toBe(true); - - }); - - /** - * TEST 2: Login avec credentials invalides - */ - test('should show error with invalid credentials', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - - // Remplir avec des credentials invalides - await fillField(page, 'input[type="email"], input[name="email"]', 'wrong@example.com'); - await fillField(page, 'input[type="password"], input[name="password"]', 'wrongpassword'); - - // Soumettre le formulaire - await forceSubmitForm(page, 'form'); - - // Attendre le message d'erreur - // Verify error message (handles both invalid credentials and locked account) - const errorMessage = await waitForToast(page, 'error', 10000); - expect(errorMessage.toLowerCase()).toMatch(/invalid|locked/); - - // Vérifier que l'utilisateur reste sur /login - await expect(page).toHaveURL(/\/login/); - }); - - /** - * TEST 2b: Login with 2FA — runs when E2E_2FA_CODE is set (optionally E2E_2FA_EMAIL, E2E_2FA_PASSWORD). - * Requires a test account with 2FA enabled; code must be valid at run time. - */ - test('should complete login with 2FA code', async ({ page }) => { - test.skip(!process.env.E2E_2FA_CODE, 'Set E2E_2FA_CODE (and optionally E2E_2FA_EMAIL, E2E_2FA_PASSWORD) to run'); - const email = process.env.E2E_2FA_EMAIL || TEST_USERS.default.email; - const password = process.env.E2E_2FA_PASSWORD || process.env.TEST_PASSWORD || TEST_USERS.default.password; - const code = process.env.E2E_2FA_CODE!; - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - await page.waitForTimeout(500); - - await fillField(page, 'input[type="email"], input[name="email"]', email); - await fillField(page, 'input[type="password"], input[name="password"]', password); - await forceSubmitForm(page, 'form'); - - await page.waitForTimeout(2000); - const twoFaInput = page.locator('input#2fa-code, input[placeholder="000000"]').first(); - await expect(twoFaInput).toBeVisible({ timeout: 10000 }); - await twoFaInput.fill(code); - const verifyButton = page.locator('button:has-text("Verify")').first(); - await expect(verifyButton).toBeVisible({ timeout: 5000 }); - await verifyButton.click(); - - await expect(page).toHaveURL(/\/(dashboard|$)/, { timeout: 15000 }); - const token = await getAuthToken(page); - expect(token).toBeTruthy(); - }); - - /** - * TEST 3: Registration (Inscription) - */ - test('should register a new user successfully', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('input[name="email"], input#email').first()).toBeVisible({ timeout: 5000 }); - - // Générer un email unique pour éviter les conflits - const uniqueEmail = `test-${Date.now()}@example.com`; - const username = `testuser${Date.now()}`; - const password = 'Str0ng!P@ssw0rd2024'; // 12+ caractères requis, fort - - // Remplir le formulaire d'inscription (4 champs: email, username, password, password_confirm) - await fillField(page, 'input[name="email"], input#email', uniqueEmail); - await page.waitForTimeout(200); // Laisser React Hook Form traiter - - await fillField(page, 'input[name="username"], input#username', username); - await page.waitForTimeout(200); // Laisser React Hook Form traiter - - await fillField(page, 'input[name="password"], input#password', password); - await page.waitForTimeout(200); // Laisser React Hook Form traiter - - // Sélecteur flexible pour couvrir toutes les variantes de nommage - // T0188: Use data-testid for robust selection as passwordConfirm was not found earlier - const confirmInput = page.getByTestId('password-confirm-input'); - if (await confirmInput.isVisible()) { - await confirmInput.fill(password); - } else { - // Fallback to name/id selectors if testid not found - await fillField(page, 'input[name="passwordConfirm"], input[name="password_confirm"], input[name="confirmPassword"], input#passwordConfirm', password); - } - - // CRITIQUE: Attendre que React Hook Form mette à jour son état - // Sans cela, le backend peut recevoir un objet incomplet - await page.waitForTimeout(500); - - // Soumettre le formulaire - await forceSubmitForm(page, 'form'); - - // ⚠️ FLEXIBLE: Wait for EITHER navigation OR auth state change - // Some implementations navigate, some just update state - const navigationSuccess = await Promise.race([ - page.waitForURL((url) => url.pathname === '/dashboard' || url.pathname === '/login', { - timeout: 10000, - }).then(() => true).catch(() => false), - page.waitForTimeout(10000).then(() => false), - ]); - - if (navigationSuccess) { - // Navigation occurred - check URL - const currentUrl = page.url(); - if (currentUrl.includes('dashboard') || !currentUrl.includes('login')) { - await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({ - timeout: 10000, - }); - } else { - // Redirected to login after registration - } - } else { - // No navigation - check if auth state was updated - const isAuthenticated = await page.evaluate(() => { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } - } catch { - return false; - } - return false; - }); - - if (isAuthenticated) { - expect(isAuthenticated).toBe(true); - } else { - // Check if we at least left the register page - const currentUrl = page.url(); - const stillOnRegister = currentUrl.includes('/register'); - if (!stillOnRegister) { - expect(stillOnRegister).toBe(false); - } else { - // Still on register, check for success message - const successMessage = await page - .locator('text=/success|registered|created|account created/i, [role="status"]') - .isVisible({ timeout: 3000 }) - .catch(() => false); - expect(successMessage).toBe(true); - } - } - } - }); - - /** - * TEST 4: Registration avec email déjà utilisé - */ - test('should show error when registering with existing email', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - - // Utiliser un email qui existe déjà (celui du test user) - const password = 'Str0ng!P@ssw0rd2024'; // 12+ caractères requis, fort - const username = 'existinguser'; - - await fillField(page, 'input[name="email"], input#email', TEST_USERS.default.email); - await page.waitForTimeout(200); - - await fillField(page, 'input[name="username"], input#username', username); - await page.waitForTimeout(200); - - await fillField(page, 'input[name="password"], input#password', password); - await page.waitForTimeout(200); - - // Sélecteur flexible pour couvrir toutes les variantes de nommage (data-testid prioritaire) - const confirmInputExisting = page.getByTestId('password-confirm-input'); - if (await confirmInputExisting.isVisible()) { - await confirmInputExisting.fill(password); - } else { - await fillField(page, 'input[name="passwordConfirm"], input[name="password_confirm"], input[name="confirmPassword"], input#passwordConfirm', password); - } - - // CRITIQUE: Attendre que React Hook Form mette à jour l'état - // Sans cela, le backend reçoit "password_confirm is required" - await page.waitForTimeout(800); - - // Soumettre le formulaire - await forceSubmitForm(page, 'form'); - - // Attendre le message d'erreur (timeout plus long car le backend doit répondre) - await page.waitForTimeout(2000); - - // 🔴 FLEXIBLE: Wait for ANY error alert (more flexible than specific text) - // Accept any visible error indicator since backend may return 500 or different error formats - const errorMessage = page.locator('.text-red-500, [role="alert"], .text-destructive, .text-red-700, .bg-red-100').first(); - const isErrorVisible = await errorMessage.isVisible({ timeout: 10000 }).catch(() => false); - - if (isErrorVisible) { - const errorText = await errorMessage.textContent(); - expect(errorText?.toLowerCase()).toMatch(/(exist|already|déjà|utilisé|taken|failed|erreur|error)/); - } else { - await expect(page).toHaveURL(/\/register/); - } - }); - - /** - * TEST 5: Logout - */ - test('should logout successfully', async ({ page }) => { - // D'abord se connecter - await loginAsUser(page); - - // Attendre que le sidebar soit visible - await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({ - timeout: 10000, - }); - - const tokenBeforeLogout = await getAuthToken(page); - expect(tokenBeforeLogout).toBeTruthy(); - - // Trouver le bouton de logout (peut être dans un menu utilisateur) - // Chercher plusieurs variantes - let logoutButton = page - .locator('button:has-text("Déconnexion"), button:has-text("Logout"), button:has-text("Se déconnecter"), button:has-text("Sign Out")') - .first(); - - // Si pas visible directement, chercher dans un menu dropdown (Avatar > Logout) - const isLogoutVisible = await logoutButton.isVisible().catch(() => false); - - if (!isLogoutVisible) { - // Ouvrir le menu utilisateur (Avatar, Profile button, etc.) - const userMenu = page - .locator('[data-testid="user-menu"], button[aria-label*="user" i], button[aria-label*="profile" i]') - .first(); - - const isUserMenuVisible = await userMenu.isVisible().catch(() => false); - - if (isUserMenuVisible) { - await expect(userMenu).toBeVisible({ timeout: 5000 }); - await userMenu.click(); - await page.waitForTimeout(500); // Attendre que le menu s'ouvre - - // Maintenant chercher le logout dans le menu - logoutButton = page - .locator('[role="menuitem"]:has-text("Déconnexion"), [role="menuitem"]:has-text("Logout"), [role="menuitem"]:has-text("Sign Out")') - .first(); - } - } - - // Vérifier que le bouton de logout est maintenant visible - await expect(logoutButton).toBeVisible({ timeout: 5000 }); - - // 🔴 CRITIQUE: Attendre que la page soit complètement chargée avant logout - // Cela évite les erreurs 400 si le header Authorization n'est pas encore prêt - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {}); - - // Attendre un peu plus pour que Axios/API client soit complètement initialisé - await page.waitForTimeout(1000); - - // Attendre la redirection vers /login après logout - const navigationPromise = page.waitForURL(/\/login/, { timeout: 10000 }); - - await logoutButton.click(); - await navigationPromise; - - // Vérifier que l'utilisateur est redirigé vers /login - await expect(page).toHaveURL(/\/login/); - - const token = await getAuthToken(page); - expect(token).toBeNull(); - }); - - /** - * TEST 6: Route Guard - Redirection vers /login si non authentifié - */ - test('should redirect to login when accessing protected route without auth', async ({ page }) => { - // S'assurer qu'il n'y a pas de token dans le localStorage - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.evaluate(() => localStorage.clear()); - - // Tenter d'accéder à une route protégée - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - - // Attendre la redirection vers /login - await page.waitForURL(/\/login/, { timeout: 10000 }); - - await expect(page).toHaveURL(/\/login/); - }); - - /** - * TEST 7: Persistance de l'authentification après refresh - */ - test('should persist authentication after page refresh', async ({ page }) => { - test.setTimeout(90000); // CI can be slow; allow extra time for login + refresh - // Wait before login to avoid rate limiting (429) - // Les tests précédents ont pu consommer le quota de login - await page.waitForTimeout(10000); - - // Login successfully - await loginAsUser(page); - - // Verify authenticated before refresh - const beforeRefresh = await page.evaluate(() => { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } - } catch { - return false; - } - return false; - }); - expect(beforeRefresh).toBe(true); - - // Refresh page - await page.reload({ waitUntil: 'domcontentloaded' }); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {}); - await page.waitForTimeout(2000); // Wait for app to check auth status - - // Verify nav/sidebar visible (confirms authenticated UI) - await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({ timeout: 10000 }); - - // Check if still authenticated - const afterRefresh = await page.evaluate(() => { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } - } catch { - return false; - } - return false; - }); - - // Check if token exists in localStorage (using helper) - const token = await getAuthToken(page); - - expect(afterRefresh).toBe(true); - expect(token).toBeTruthy(); - }); - - /** - * TEST 8: Validation du formulaire de login - */ - test('should validate login form fields', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - - // Wait for form to be ready - await page.waitForSelector('form', { state: 'visible', timeout: 10000 }); - - const initialUrl = page.url(); - - // Fill with INVALID data to trigger validation - const emailInput = page.locator('input[type="email"], input[name="email"]').first(); - await emailInput.fill('not-an-email'); // Invalid email - await page.waitForTimeout(200); - - // Try submitting the form with invalid data - const submitButton = page.locator('button[type="submit"]').first(); - await expect(submitButton).toBeVisible({ timeout: 5000 }); - await submitButton.click(); - await page.waitForTimeout(2000); // Wait to see if navigation happens - - // VALIDATION STRATEGY: If validation works, we should STAY on the login page - // (form submission should be blocked) - const currentUrl = page.url(); - const stayedOnLoginPage = currentUrl === initialUrl || currentUrl.includes('/login'); - - // Try to find visible error messages - const emailError = await page - .locator('text=/email.*invalide|invalid/i, p.text-red-500, p.text-destructive, .text-red-500, .error-message') - .first() - .isVisible({ timeout: 1000 }) - .catch(() => false); - - const passwordError = await page - .locator('text=/password.*required|requis/i, p.text-red-500, p.text-destructive, .text-red-500, .error-message') - .first() - .isVisible({ timeout: 1000 }) - .catch(() => false); - - // Validation is working if EITHER: - // 1. An error message is visible OR - // 2. We stayed on the login page (form blocked from submitting) - const validationWorking = emailError || passwordError || stayedOnLoginPage; - expect(validationWorking).toBeTruthy(); - }); - - /** - * TEST 9: Validation du formulaire d'inscription (mots de passe différents) - */ - test('should show error when passwords do not match during registration', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - - // Remplir avec des mots de passe différents - await fillField(page, 'input[name="email"], input#email', 'newuser@example.com'); - await page.waitForTimeout(200); - - await fillField(page, 'input[name="password"], input#password', 'Password123456!'); // 12+ chars - await page.waitForTimeout(200); - - // Sélecteur flexible pour couvrir toutes les variantes de nommage - await fillField(page, 'input[name="passwordConfirm"], input[name="password_confirm"], input[name="confirmPassword"]', 'DifferentPassword!'); - - // Attendre que React Hook Form valide - await page.waitForTimeout(500); - - // Soumettre le formulaire (ou attendre que la validation se déclenche) - // Note: React Hook Form peut bloquer la soumission si validation échoue - await forceSubmitForm(page, 'form').catch(() => {}); - - // Attendre le message d'erreur (validation côté client Zod/React Hook Form) - // Le message peut apparaître sans soumission si validation inline - await page.waitForTimeout(1500); // Augmenté pour React Hook Form - - // Chercher les sélecteurs d'erreur de validation de manière plus robuste - const errorVisible = await page - .locator('.text-destructive, [role="alert"], .text-red-500, .text-red-600, .error-message, p.text-sm.text-destructive') - .first() - .isVisible({ timeout: 8000 }) - .catch(() => false); - - // Alternative: chercher aussi par texte si le sélecteur CSS échoue - if (!errorVisible) { - const errorByText = await page - .locator('text=/password.*match|correspondent|identique|same/i') - .first() - .isVisible({ timeout: 3000 }) - .catch(() => false); - - expect(errorByText).toBeTruthy(); - } else { - expect(errorVisible).toBeTruthy(); - } - }); - - test.afterEach(async ({ }, testInfo) => { - if (consoleErrors.length > 0 && testInfo.status === 'passed') { - testInfo.annotations.push({ type: 'console-errors', description: consoleErrors.join('; ') }); - } - if (networkErrors.length > 0 && testInfo.status === 'passed') { - testInfo.annotations.push({ - type: 'network-errors', - description: networkErrors.map((e) => `${e.method} ${e.url}: ${e.status}`).join('; '), - }); - } - }); -}); diff --git a/apps/web/e2e/tests/chat.spec.ts b/apps/web/e2e/tests/chat.spec.ts deleted file mode 100644 index 31df90fcd..000000000 --- a/apps/web/e2e/tests/chat.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { TEST_CONFIG, loginAsUser, setupErrorCapture } from '../utils/test-helpers'; - -/** - * Chat E2E Test Suite (Audit 2.10) - * - * Couvre le flow critique du chat : - * - Load chat page et UI (sidebar, channels, input) - * - État déconnecté quand WebSocket indisponible - * - Envoi de message quand WebSocket connecté (skip si non connecté) - */ - -test.describe('Chat Flow', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - test('should load chat page and show UI', async ({ page }) => { - console.log('🧪 [CHAT] Running: Load chat page and show UI'); - - await loginAsUser(page); - await page.waitForTimeout(1000); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/chat`); - await page.waitForLoadState('domcontentloaded'); - - await page.waitForTimeout(3000); - - const channelsHeader = page.locator('h3:has-text("Channels")'); - const loadingState = page.locator('text=/ESTABLISHING UPLINK|Establishing/i'); - const errorState = page.locator('text=/Connection Terminated|Access Restricted/i'); - const messageInput = page.locator('input[placeholder*="Broadcast"], input[aria-label*="message" i]'); - - const hasChannels = await channelsHeader.isVisible({ timeout: 5000 }).catch(() => false); - const hasLoading = await loadingState.isVisible({ timeout: 2000 }).catch(() => false); - const hasError = await errorState.isVisible({ timeout: 2000 }).catch(() => false); - const hasInput = await messageInput.isVisible({ timeout: 5000 }).catch(() => false); - - expect(hasChannels || hasLoading || hasError).toBeTruthy(); - - if (hasChannels) { - expect(hasInput).toBeTruthy(); - console.log('✅ [CHAT] Chat UI loaded with Channels and input'); - } else if (hasLoading) { - console.log('✅ [CHAT] Chat page showing loading state'); - } else if (hasError) { - console.log('✅ [CHAT] Chat page showing error state (expected when chat server unavailable)'); - } - }); - - test('should show disconnected state when WebSocket unavailable', async ({ page }) => { - console.log('🧪 [CHAT] Running: Disconnected state indicator'); - - await loginAsUser(page); - await page.waitForTimeout(1000); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/chat`); - await page.waitForLoadState('domcontentloaded'); - - await page.waitForTimeout(5000); - - const channelsHeader = page.locator('h3:has-text("Channels")'); - const statusDot = page.locator('.rounded-full.w-2.h-2, div.w-2.h-2.rounded-full'); - - const hasChannels = await channelsHeader.isVisible({ timeout: 10000 }).catch(() => false); - test.skip(!hasChannels, 'Chat UI not loaded - may be loading or error state'); - - const hasStatusDot = await statusDot.first().isVisible({ timeout: 3000 }).catch(() => false); - expect(hasStatusDot).toBeTruthy(); - - const dotClasses = await statusDot.first().getAttribute('class').catch(() => ''); - const isDisconnected = dotClasses?.includes('bg-destructive') ?? false; - const isConnected = dotClasses?.includes('bg-success') ?? false; - - expect(isDisconnected || isConnected).toBeTruthy(); - console.log(`✅ [CHAT] Status indicator visible (connected: ${isConnected}, disconnected: ${isDisconnected})`); - }); - - test('should send and display message when WebSocket connected', async ({ page }) => { - console.log('🧪 [CHAT] Running: Send message (when WebSocket connected)'); - - await loginAsUser(page); - await page.waitForTimeout(1000); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/chat`); - await page.waitForLoadState('domcontentloaded'); - - await page.waitForTimeout(5000); - - const channelsHeader = page.locator('h3:has-text("Channels")'); - const statusDot = page.locator('div.w-2.h-2.rounded-full, .rounded-full.w-2.h-2').first(); - const conversationItem = page.locator('[data-testid="conversation-item"], button, [role="button"]').filter({ hasText: /general|default|lobby|channel/i }).first(); - const messageInput = page.locator('input[placeholder*="Broadcast"], input[aria-label*="message" i]'); - const sendButton = page.locator('button[type="submit"]').filter({ has: page.locator('svg') }); - - const hasChannels = await channelsHeader.isVisible({ timeout: 10000 }).catch(() => false); - test.skip(!hasChannels, 'Chat UI not loaded'); - - const dotClasses = await statusDot.getAttribute('class').catch(() => ''); - const isConnected = dotClasses?.includes('bg-success') ?? false; - test.skip(!isConnected, 'WebSocket not connected - chat server may be unavailable'); - - const hasConversation = await conversationItem.isVisible({ timeout: 3000 }).catch(() => false); - test.skip(!hasConversation, 'No conversation/channel available to send message'); - - await conversationItem.click(); - await page.waitForTimeout(500); - - const testMessage = `E2E test ${Date.now()}`; - await messageInput.fill(testMessage); - await page.waitForTimeout(300); - - await sendButton.click(); - await page.waitForTimeout(2000); - - const messageVisible = await page.locator(`text="${testMessage}"`).isVisible({ timeout: 5000 }).catch(() => false); - expect(messageVisible).toBeTruthy(); - console.log('✅ [CHAT] Message sent and displayed'); - }); - - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [CHAT] === Final Verifications ==='); - if (consoleErrors.length > 0) { - console.log(`🔴 [CHAT] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((e) => console.log(` - ${e}`)); - } - if (networkErrors.length > 0) { - console.log(`🔴 [CHAT] Network errors (${networkErrors.length}):`); - networkErrors.forEach((e) => console.log(` - ${e.method} ${e.url}: ${e.status}`)); - } - }); -}); diff --git a/apps/web/e2e/tests/cross-browser.spec.ts b/apps/web/e2e/tests/cross-browser.spec.ts deleted file mode 100644 index 0b5304036..000000000 --- a/apps/web/e2e/tests/cross-browser.spec.ts +++ /dev/null @@ -1,302 +0,0 @@ - -import { test, expect } from '@playwright/test'; -import { TEST_CONFIG } from '../utils/test-helpers'; - -/** - * Cross-Browser Tests - * - * These tests verify that core functionality works across different browsers: - * - Chromium (Chrome, Edge) - * - Firefox - * - WebKit (Safari) - * - * These tests run on all browsers configured in playwright.config.ts - * - * To run cross-browser tests: - * - Run: npx playwright test cross-browser - * - Run on specific browser: npx playwright test cross-browser --project=firefox - */ - -test.describe('Cross-Browser Compatibility', () => { - // Use authenticated state for most tests - test.use({ storageState: 'e2e/.auth/user.json' }); - - test.describe('Authentication', () => { - test('should login successfully on all browsers', async ({ page, browserName }) => { - // Use unauthenticated state for login test - await page.context().clearCookies(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - // Wait for form to be ready - await page.waitForSelector('form', { timeout: 5000 }); - await page.waitForTimeout(500); - - // Fill login form - await page.fill('input[type="email"], input[name="email"]', TEST_CONFIG.TEST_USER_EMAIL); - await page.fill('input[type="password"], input[name="password"]', TEST_CONFIG.TEST_USER_PASSWORD); - - // Submit form - await page.click('button[type="submit"], button:has-text("Login"), button:has-text("Sign in")'); - - // Wait for navigation to dashboard - await page.waitForURL('**/dashboard', { timeout: 10000 }); - - // Verify we're on dashboard - expect(page.url()).toContain('/dashboard'); - - console.log(`✅ Login successful on ${browserName}`); - }); - - test('should display login form correctly on all browsers', async ({ page, browserName }) => { - await page.context().clearCookies(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - // Check that form elements are visible - const emailInput = page.locator('input[type="email"], input[name="email"]').first(); - const passwordInput = page.locator('input[type="password"], input[name="password"]').first(); - const submitButton = page.locator('button[type="submit"]').first(); - - await expect(emailInput).toBeVisible(); - await expect(passwordInput).toBeVisible(); - await expect(submitButton).toBeVisible(); - - console.log(`✅ Login form displayed correctly on ${browserName}`); - }); - }); - - test.describe('Navigation', () => { - test('should navigate between pages on all browsers', async ({ page, browserName }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Navigate to profile - await page.click('a[href="/profile"], a[href*="profile"]', { timeout: 5000 }); - await page.waitForURL('**/profile', { timeout: 5000 }); - expect(page.url()).toContain('/profile'); - - // Navigate back to dashboard - await page.click('a[href="/dashboard"], a[href*="dashboard"]', { timeout: 5000 }); - await page.waitForURL('**/dashboard', { timeout: 5000 }); - expect(page.url()).toContain('/dashboard'); - - console.log(`✅ Navigation works on ${browserName}`); - }); - - test('should handle browser back/forward buttons', async ({ page, browserName }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Navigate to profile - await page.click('a[href="/profile"], a[href*="profile"]', { timeout: 5000 }); - await page.waitForURL('**/profile', { timeout: 5000 }); - - // Use browser back button - await page.goBack(); - await page.waitForURL('**/dashboard', { timeout: 5000 }); - expect(page.url()).toContain('/dashboard'); - - // Use browser forward button - await page.goForward(); - await page.waitForURL('**/profile', { timeout: 5000 }); - expect(page.url()).toContain('/profile'); - - console.log(`✅ Browser navigation works on ${browserName}`); - }); - }); - - test.describe('UI Components', () => { - test('should render buttons correctly on all browsers', async ({ page, browserName }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Find buttons on the page - const buttons = page.locator('button').first(); - await expect(buttons).toBeVisible(); - - // Check button styling (basic check) - const buttonStyles = await buttons.evaluate((el) => { - const styles = window.getComputedStyle(el); - return { - display: styles.display, - visibility: styles.visibility, - }; - }); - - expect(buttonStyles.display).not.toBe('none'); - expect(buttonStyles.visibility).not.toBe('hidden'); - - console.log(`✅ Buttons render correctly on ${browserName}`); - }); - - test('should render forms correctly on all browsers', async ({ page, browserName }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('networkidle'); - - // Wait for form elements - await page.waitForTimeout(1000); - - // Check for input fields - const inputs = page.locator('input, textarea, select'); - const inputCount = await inputs.count(); - - // Should have at least some form elements - expect(inputCount).toBeGreaterThan(0); - - console.log(`✅ Forms render correctly on ${browserName}`); - }); - }); - - test.describe('JavaScript Features', () => { - test('should support ES6+ features on all browsers', async ({ page, browserName }) => { - const result = await page.evaluate(() => { - // Test various ES6+ features - const features = { - arrowFunctions: typeof (() => { }) === 'function', - promises: typeof Promise !== 'undefined', - asyncAwait: typeof (async () => { }) === 'function', - templateLiterals: typeof `test` === 'string', - destructuring: (() => { - try { - const { a } = { a: 1 }; - return a === 1; - } catch { - return false; - } - })(), - spreadOperator: (() => { - try { - const arr = [...[1, 2, 3]]; - return arr.length === 3; - } catch { - return false; - } - })(), - }; - return features; - }); - - // All modern browsers should support these features - expect(result.arrowFunctions).toBe(true); - expect(result.promises).toBe(true); - expect(result.asyncAwait).toBe(true); - expect(result.templateLiterals).toBe(true); - expect(result.destructuring).toBe(true); - expect(result.spreadOperator).toBe(true); - - console.log(`✅ ES6+ features supported on ${browserName}`); - }); - - test('should support Web APIs on all browsers', async ({ page, browserName }) => { - const result = await page.evaluate(() => { - return { - fetch: typeof fetch !== 'undefined', - localStorage: typeof localStorage !== 'undefined', - sessionStorage: typeof sessionStorage !== 'undefined', - webSocket: typeof WebSocket !== 'undefined', - history: typeof window.history !== 'undefined' && typeof window.history.pushState === 'function', - }; - }); - - // All modern browsers should support these APIs - expect(result.fetch).toBe(true); - expect(result.localStorage).toBe(true); - expect(result.sessionStorage).toBe(true); - expect(result.webSocket).toBe(true); - expect(result.history).toBe(true); - - console.log(`✅ Web APIs supported on ${browserName}`); - }); - }); - - test.describe('CSS Features', () => { - test('should support modern CSS features on all browsers', async ({ page, browserName }) => { - const result = await page.evaluate(() => { - const testElement = document.createElement('div'); - testElement.style.cssText = 'display: flex; grid-template-columns: 1fr; transform: translateX(0);'; - document.body.appendChild(testElement); - - const styles = window.getComputedStyle(testElement); - const supported = { - flexbox: styles.display === 'flex' || styles.display === '-webkit-flex', - grid: styles.gridTemplateColumns !== undefined, - transform: styles.transform !== 'none' || styles.webkitTransform !== 'none', - }; - - document.body.removeChild(testElement); - return supported; - }); - - // All modern browsers should support these CSS features - expect(result.flexbox).toBe(true); - expect(result.grid).toBe(true); - expect(result.transform).toBe(true); - - console.log(`✅ Modern CSS features supported on ${browserName}`); - }); - }); - - test.describe('Responsive Design', () => { - test('should be responsive on all browsers', async ({ page, browserName }) => { - // Test mobile viewport - await page.setViewportSize({ width: 375, height: 667 }); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Check that page is visible and not broken - const body = page.locator('body'); - await expect(body).toBeVisible(); - - // Test tablet viewport - await page.setViewportSize({ width: 768, height: 1024 }); - await page.reload(); - await page.waitForLoadState('networkidle'); - await expect(body).toBeVisible(); - - // Test desktop viewport - await page.setViewportSize({ width: 1920, height: 1080 }); - await page.reload(); - await page.waitForLoadState('networkidle'); - await expect(body).toBeVisible(); - - console.log(`✅ Responsive design works on ${browserName}`); - }); - }); - - test.describe('Error Handling', () => { - test('should handle errors gracefully on all browsers', async ({ page, browserName }) => { - // Navigate to a non-existent page - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-page-12345`); - await page.waitForLoadState('networkidle'); - - // Should show 404 page or error message, not blank page - const body = page.locator('body'); - const bodyText = await body.textContent(); - - expect(bodyText).not.toBe(''); - expect(bodyText).not.toBeNull(); - - console.log(`✅ Error handling works on ${browserName}`); - }); - }); - - test.describe('Performance', () => { - test('should load pages within acceptable time on all browsers', async ({ page, browserName }) => { - const startTime = Date.now(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const loadTime = Date.now() - startTime; - - // Should load within 10 seconds (generous threshold for cross-browser) - expect(loadTime).toBeLessThan(10000); - - console.log(`✅ Page loaded in ${loadTime}ms on ${browserName}`); - }); - }); -}); - diff --git a/apps/web/e2e/tests/mobile.spec.ts b/apps/web/e2e/tests/mobile.spec.ts deleted file mode 100644 index bc0613006..000000000 --- a/apps/web/e2e/tests/mobile.spec.ts +++ /dev/null @@ -1,376 +0,0 @@ - -import { test, expect } from '@playwright/test'; -import { TEST_CONFIG } from '../utils/test-helpers'; - -/** - * Mobile Responsive Tests - * - * These tests verify that the application works correctly on various mobile device sizes. - * Tests cover: - * - Small phones (iPhone SE, small Android) - * - Medium phones (iPhone 12/13, standard Android) - * - Large phones (iPhone Pro Max, large Android) - * - Small tablets (iPad Mini) - * - * To run mobile responsive tests: - * - Run: npx playwright test mobile-responsive - * - Run on specific device: npx playwright test mobile-responsive --project="iPhone 12" - */ - -// Common mobile viewport sizes -const MOBILE_VIEWPORTS = { - 'iPhone SE': { width: 375, height: 667 }, // Small phone - 'iPhone 12': { width: 390, height: 844 }, // Medium phone - 'iPhone 14 Pro Max': { width: 430, height: 932 }, // Large phone - 'Samsung Galaxy S21': { width: 360, height: 800 }, // Android medium - 'Pixel 5': { width: 393, height: 851 }, // Android medium - 'iPad Mini': { width: 768, height: 1024 }, // Small tablet -}; - -test.describe('Mobile Responsive Tests', () => { - // Use authenticated state for most tests - test.use({ storageState: 'e2e/.auth/user.json' }); - - test.describe('Small Phone (iPhone SE - 375x667)', () => { - test.beforeEach(async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['iPhone SE']); - }); - - test('dashboard should be usable on small phone', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Check that main content is visible - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // Check that navigation is accessible (hamburger menu or similar) - const navButton = page.locator('button[aria-label*="menu"], button[aria-label*="Menu"], [data-testid*="menu"]').first(); - if (await navButton.count() > 0) { - await expect(navButton).toBeVisible(); - } - - // Verify no horizontal scrolling - const bodyWidth = await page.evaluate(() => document.body.scrollWidth); - const viewportWidth = MOBILE_VIEWPORTS['iPhone SE'].width; - expect(bodyWidth).toBeLessThanOrEqual(viewportWidth + 10); // Allow small margin - }); - - test('login page should be usable on small phone', async ({ page }) => { - await page.context().clearCookies(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - // Check form elements are visible and accessible - const emailInput = page.locator('input[type="email"], input[name="email"]').first(); - const passwordInput = page.locator('input[type="password"], input[name="password"]').first(); - const submitButton = page.locator('button[type="submit"]').first(); - - await expect(emailInput).toBeVisible(); - await expect(passwordInput).toBeVisible(); - await expect(submitButton).toBeVisible(); - - // Check that inputs are large enough to tap (min 44x44px recommended) - const emailBox = await emailInput.boundingBox(); - const passwordBox = await passwordInput.boundingBox(); - const buttonBox = await submitButton.boundingBox(); - - if (emailBox) expect(emailBox.height).toBeGreaterThanOrEqual(40); - if (passwordBox) expect(passwordBox.height).toBeGreaterThanOrEqual(40); - if (buttonBox) expect(buttonBox.height).toBeGreaterThanOrEqual(40); - }); - - test('profile page should be usable on small phone', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // Check that form elements are accessible - const inputs = page.locator('input, textarea, select'); - const inputCount = await inputs.count(); - expect(inputCount).toBeGreaterThan(0); - }); - }); - - test.describe('Medium Phone (iPhone 12 - 390x844)', () => { - test.beforeEach(async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['iPhone 12']); - }); - - test('dashboard should render correctly on medium phone', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // Take screenshot for visual verification - await expect(page).toHaveScreenshot('dashboard-iphone12.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - - test('navigation should work on medium phone', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Try to navigate to profile - const profileLink = page.locator('a[href="/profile"], a[href*="profile"]').first(); - if (await profileLink.count() > 0) { - await profileLink.click({ timeout: 5000 }); - await page.waitForURL('**/profile', { timeout: 5000 }); - expect(page.url()).toContain('/profile'); - } - }); - - test('tracks page should be usable on medium phone', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/tracks`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // Check that content is scrollable if needed - const isScrollable = await page.evaluate(() => { - return document.documentElement.scrollHeight > window.innerHeight; - }); - - // Should be able to scroll if content is long - expect(typeof isScrollable).toBe('boolean'); - }); - }); - - test.describe('Large Phone (iPhone 14 Pro Max - 430x932)', () => { - test.beforeEach(async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['iPhone 14 Pro Max']); - }); - - test('dashboard should utilize larger screen space', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // On larger phones, sidebar might be visible - const sidebar = page.locator('aside').first(); - const sidebarVisible = await sidebar.isVisible().catch(() => false); - - // Either sidebar is visible or hamburger menu is available - if (!sidebarVisible) { - const menuButton = page.locator('button[aria-label*="menu"], [data-testid*="menu"]').first(); - const menuExists = await menuButton.count() > 0; - expect(menuExists || sidebarVisible).toBe(true); - } - }); - - test('forms should be properly sized on large phone', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('networkidle'); - - const inputs = page.locator('input, textarea'); - const inputCount = await inputs.count(); - - if (inputCount > 0) { - const firstInput = inputs.first(); - const box = await firstInput.boundingBox(); - - if (box) { - // Inputs should be wide enough but not too wide - expect(box.width).toBeGreaterThan(200); - expect(box.width).toBeLessThan(430); // Should not exceed viewport - } - } - }); - }); - - test.describe('Android Devices', () => { - test('Samsung Galaxy S21 should render correctly', async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['Samsung Galaxy S21']); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - }); - - test('Pixel 5 should render correctly', async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['Pixel 5']); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - }); - }); - - test.describe('Small Tablet (iPad Mini - 768x1024)', () => { - test.beforeEach(async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['iPad Mini']); - }); - - test('dashboard should use tablet layout', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // On tablets, sidebar might be visible - const sidebar = page.locator('aside').first(); - const sidebarVisible = await sidebar.isVisible().catch(() => false); - - // Tablet should show more content - expect(sidebarVisible || true).toBe(true); // Sidebar or main content should be visible - }); - - test('forms should be properly sized on tablet', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('networkidle'); - - const form = page.locator('form').first(); - if (await form.count() > 0) { - await expect(form).toBeVisible(); - - // Forms on tablet should be wider - const formBox = await form.boundingBox(); - if (formBox) { - expect(formBox.width).toBeGreaterThan(400); - } - } - }); - }); - - test.describe('Touch Interactions', () => { - test.beforeEach(async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['iPhone 12']); - }); - - test('buttons should be tappable', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const buttons = page.locator('button').first(); - if (await buttons.count() > 0) { - const buttonBox = await buttons.boundingBox(); - - if (buttonBox) { - // Buttons should be at least 44x44px for easy tapping - expect(buttonBox.width).toBeGreaterThanOrEqual(40); - expect(buttonBox.height).toBeGreaterThanOrEqual(40); - } - } - }); - - test('links should be tappable', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const links = page.locator('a').first(); - if (await links.count() > 0) { - const linkBox = await links.boundingBox(); - - if (linkBox) { - // Links should have adequate touch target size - expect(linkBox.height).toBeGreaterThanOrEqual(30); - } - } - }); - }); - - test.describe('Orientation Changes', () => { - test('should handle portrait orientation', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); // Portrait - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - }); - - test('should handle landscape orientation', async ({ page }) => { - await page.setViewportSize({ width: 667, height: 375 }); // Landscape - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // In landscape, sidebar might be visible - const sidebar = page.locator('aside').first(); - const sidebarVisible = await sidebar.isVisible().catch(() => false); - - // Should work in both cases - expect(sidebarVisible || true).toBe(true); - }); - }); - - test.describe('Responsive Breakpoints', () => { - test('should adapt to different breakpoints', async ({ page }) => { - const breakpoints = [ - { width: 320, height: 568, name: 'Very Small' }, - { width: 375, height: 667, name: 'Small' }, - { width: 414, height: 896, name: 'Medium' }, - { width: 768, height: 1024, name: 'Tablet' }, - ]; - - for (const breakpoint of breakpoints) { - await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - const main = page.locator('main, [role="main"]').first(); - await expect(main).toBeVisible(); - - // Verify no horizontal overflow - const bodyWidth = await page.evaluate(() => document.body.scrollWidth); - expect(bodyWidth).toBeLessThanOrEqual(breakpoint.width + 20); // Allow small margin - - console.log(`✅ ${breakpoint.name} (${breakpoint.width}x${breakpoint.height}) - OK`); - } - }); - }); - - test.describe('Mobile-Specific Features', () => { - test.beforeEach(async ({ page }) => { - await page.setViewportSize(MOBILE_VIEWPORTS['iPhone 12']); - }); - - test('should handle mobile viewport meta tag', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - - const viewport = await page.locator('meta[name="viewport"]').getAttribute('content'); - - // Should have viewport meta tag for mobile - expect(viewport).toBeTruthy(); - }); - - test('should prevent zoom on input focus', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - const input = page.locator('input').first(); - if (await input.count() > 0) { - await input.focus(); - - // Check that font-size is at least 16px to prevent zoom on iOS - const fontSize = await input.evaluate((el) => { - return window.getComputedStyle(el).fontSize; - }); - - const fontSizeNum = parseFloat(fontSize); - expect(fontSizeNum).toBeGreaterThanOrEqual(14); // At least 14px to prevent zoom - } - }); - }); -}); - diff --git a/apps/web/e2e/tests/mobile.spec.ts-snapshots/dashboard-iphone12-chromium-linux.png b/apps/web/e2e/tests/mobile.spec.ts-snapshots/dashboard-iphone12-chromium-linux.png deleted file mode 100644 index 27c0ff4f1..000000000 Binary files a/apps/web/e2e/tests/mobile.spec.ts-snapshots/dashboard-iphone12-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/mobile.spec.ts-snapshots/dashboard-iphone12-msedge-linux.png b/apps/web/e2e/tests/mobile.spec.ts-snapshots/dashboard-iphone12-msedge-linux.png deleted file mode 100644 index a999727e3..000000000 Binary files a/apps/web/e2e/tests/mobile.spec.ts-snapshots/dashboard-iphone12-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/play.spec.ts b/apps/web/e2e/tests/play.spec.ts deleted file mode 100644 index dcc8ed81d..000000000 --- a/apps/web/e2e/tests/play.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Play flow E2E: after login, go to library or search, click a track, verify track page or player visible. - */ - -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - setupErrorCapture, -} from '../utils/test-helpers'; - -test.describe('Play Flow', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - test('after login, search or library -> click track -> track page or player visible', async ({ page }) => { - test.setTimeout(60000); - - await loginAsUser(page); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/search`, { waitUntil: 'domcontentloaded' }); - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - - const searchInput = page.locator( - 'input[type="search"], input[placeholder*="Search" i], input[placeholder*="Recherche" i], input[name="q"]' - ).first(); - await searchInput.fill('test').catch(() => {}); - await page.waitForTimeout(1000); - - const trackLink = page.locator('a[href*="/tracks/"]').first(); - const hasTrack = await trackLink.isVisible({ timeout: 5000 }).catch(() => false); - - if (hasTrack) { - await trackLink.click(); - await page.waitForURL(/\/tracks\//, { timeout: 10000 }).catch(() => {}); - const onTrackPage = (await page.url()).includes('/tracks/'); - expect(onTrackPage).toBe(true); - - const trackPageOrPlayer = page.locator( - '[data-testid="track-detail"], [data-testid="track-detail-page"], main, .fixed.bottom-0' - ).first(); - await expect(trackPageOrPlayer).toBeVisible({ timeout: 10000 }); - } else { - const resultsArea = page.locator('main, [data-testid="search-results"], [aria-label*="search" i]').first(); - const noResults = page.getByText(/no results|aucun résultat/i); - const hasContent = await resultsArea.isVisible({ timeout: 3000 }).catch(() => false) - || await noResults.isVisible({ timeout: 2000 }).catch(() => false); - expect(hasContent).toBe(true); - } - }); -}); diff --git a/apps/web/e2e/tests/playlists.spec.ts b/apps/web/e2e/tests/playlists.spec.ts deleted file mode 100644 index 7370d9b6c..000000000 --- a/apps/web/e2e/tests/playlists.spec.ts +++ /dev/null @@ -1,609 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - forceSubmitForm, - openModal, - closeModal, - fillField, - safeClick, - navigateViaHref, - setupErrorCapture, - waitForToast, - waitForListLoaded, -} from '../utils/test-helpers'; - -/** - * Playlists E2E Test Suite - * - * Teste le cycle de vie complet des playlists : - * - Création d'une playlist - * - Lecture de la liste des playlists - * - Modification d'une playlist - * - Ajout de tracks à une playlist - * - Suppression de tracks d'une playlist - * - Suppression d'une playlist - */ - -test.describe('Playlists CRUD', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - - // 1. Login avant chaque test (nous laisse sur /dashboard si déjà connecté) - await loginAsUser(page); - - // 2. CORRECTION : Forcer la navigation vers la page des playlists - console.log('🧭 [NAVIGATION] Going to playlists page...'); - // 🔴 FIX: Utiliser l'URL complète pour éviter "Cannot navigate to invalid URL" - // S'assurer que TEST_CONFIG.FRONTEND_URL est défini - const baseUrl = TEST_CONFIG.FRONTEND_URL || 'http://localhost:3000'; - const playlistsUrl = `${baseUrl}/playlists`; - console.log(`🧭 [NAVIGATION] Navigating to: ${playlistsUrl}`); - await page.goto(playlistsUrl, { waitUntil: 'networkidle' }); - await page.waitForLoadState('networkidle'); - - // 🔴 FIX: Attendre que la page soit complètement chargée et hydratée - // Attendre le titre de la page ou la fin du loading - try { - await Promise.race([ - page.locator('h1:has-text("Playlist"), h1:has-text("Playlists"), h2:has-text("Playlist")').first().waitFor({ state: 'visible', timeout: 10000 }), - page.locator('[data-testid="playlists-page"], [data-testid="playlist-list"]').first().waitFor({ state: 'visible', timeout: 10000 }), - // Attendre qu'un élément de contenu soit visible (pas juste le skeleton) - page.locator('main, [role="main"]').first().waitFor({ state: 'visible', timeout: 10000 }), - ]); - console.log('✅ [PLAYLISTS] Page fully loaded'); - } catch { - console.warn('⚠️ [PLAYLISTS] Page load check timeout, continuing...'); - } - - // Attendre que les requêtes API soient terminées (si applicable) - try { - await page.waitForResponse( - (response) => response.url().includes('/playlists') && response.status() < 500, - { timeout: 10000 } - ).catch(() => { - // Si pas de requête API, ce n'est pas grave - }); - } catch { - // Ignorer si pas de requête API - } - }); - - /** - * TEST 1: Créer une nouvelle playlist - */ - test('should create a new playlist successfully', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Create new playlist'); - - // Naviguer directement vers la page des playlists (pas de lien dans sidebar) - // Utiliser l'URL complète et domcontentloaded pour éviter les timeouts - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'domcontentloaded' }); - // Attendre un peu pour que React Router mette à jour l'URL - await page.waitForTimeout(500); - // Vérifier l'URL mais ne pas timeout si elle ne change pas immédiatement - await page.waitForURL(/\/playlists/, { timeout: 15000 }).catch(() => { - // Si l'URL n'a pas changé, vérifier qu'on est au moins sur la bonne page - const currentUrl = page.url(); - if (!currentUrl.includes('/playlists')) { - throw new Error(`Navigation to /playlists failed. Current URL: ${currentUrl}`); - } - }); - - // Ouvrir la modal de création - // Le bouton a maintenant data-testid="create-playlist-btn" et aria-label="Créer une nouvelle playlist" - await openModal(page, /create|créer|nouvelle/i); - - // Remplir le formulaire - const playlistName = `Test Playlist ${Date.now()}`; - await fillField(page, 'input[name="name"], input[name="title"], input#title', playlistName); - - // Description (optionnelle) - const descriptionField = page.locator('textarea[name="description"], textarea#description').first(); - const isDescriptionVisible = await descriptionField.isVisible().catch(() => false); - - if (isDescriptionVisible) { - await descriptionField.fill('Playlist de test créée par E2E automation'); - } - - // Soumettre le formulaire - await forceSubmitForm(page, 'form'); - - // Attendre le succès - await waitForToast(page, 'success', 10000); - - // Attendre que la modal se ferme - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // 🔴 FIX: Recharger la page pour forcer le rafraîchissement de la liste - // La liste peut ne pas se rafraîchir automatiquement après création - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // 🔴 FIX: Utiliser directement getByText au lieu de waitForListLoaded - // Plus fiable car il cherche directement le texte, indépendamment de la structure UI - await expect(page.getByText(playlistName)).toBeVisible({ timeout: 15000 }); - - console.log('✅ [PLAYLISTS] Playlist created successfully'); - }); - - /** - * TEST 2: Lire la liste des playlists - */ - test('should display list of playlists', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Display playlists list'); - - // Naviguer directement vers la page des playlists (pas de lien dans sidebar) - // Utiliser l'URL complète et domcontentloaded pour éviter les timeouts - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'domcontentloaded' }); - // Attendre un peu pour que React Router mette à jour l'URL - await page.waitForTimeout(500); - // Vérifier l'URL mais ne pas timeout si elle ne change pas immédiatement - await page.waitForURL(/\/playlists/, { timeout: 15000 }).catch(() => { - // Si l'URL n'a pas changé, vérifier qu'on est au moins sur la bonne page - const currentUrl = page.url(); - if (!currentUrl.includes('/playlists')) { - throw new Error(`Navigation to /playlists failed. Current URL: ${currentUrl}`); - } - }); - - // Attendre que la liste soit chargée (peut être vide, donc minRows=0) - await waitForListLoaded(page, 0); - - // Vérifier que la page affiche le titre "Playlists" ou équivalent - const pageTitle = page.locator('h1:has-text("Playlists"), h1:has-text("Mes playlists")'); - await expect(pageTitle).toBeVisible({ timeout: 10000 }); - - // Vérifier que soit la liste est visible, soit l'état vide est affiché - const listOrEmpty = page.locator('[role="list"], [role="table"], text=/aucune|no.*found|empty|vide/i').first(); - const isVisible = await listOrEmpty.isVisible({ timeout: 5000 }).catch(() => false); - if (!isVisible) { - // Si ni liste ni état vide, vérifier au moins que le conteneur de la page est visible - const container = page.locator('.playlist-container, [data-testid="playlists-page"]').first(); - await expect(container).toBeVisible({ timeout: 5000 }); - } - - console.log('✅ [PLAYLISTS] Playlists page loaded successfully'); - }); - - /** - * TEST 3: Modifier une playlist existante - */ - test('should update playlist name and description', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Update playlist'); - - // Créer d'abord une playlist - await navigateViaHref(page, '/playlists', /\/playlists/); - await openModal(page, /create|créer|nouvelle/i); - - const originalName = `Original Playlist ${Date.now()}`; - await fillField(page, 'input[name="name"], input[name="title"], input#title', originalName); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - - // Attendre que la modal se ferme - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // 🔴 FIX: Recharger la page pour forcer le rafraîchissement de la liste - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // 🔴 FIX: Utiliser directement getByText au lieu de waitForListLoaded - // Plus fiable car il cherche directement le texte, indépendamment de la structure UI - // 🔴 FIX: Cibler le lien de la card spécifiquement - // getByText peut cibler un élément non cliquable si le CSS est complexe - const playlistCard = page.locator('a[href*="/playlists/"]').filter({ hasText: originalName }).first(); - // 🔴 FIX: Naviguer manuellement vers la page de détails pour éviter les problèmes de clic/overlay - const href = await playlistCard.getAttribute('href'); - if (!href) throw new Error('Playlist card has no href'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${href}`, { waitUntil: 'networkidle' }); - - // Attendre que la page de détails se charge (redondant mais sûr) - await page.waitForURL(/\/playlists\/[^/]+/, { timeout: 10000 }); - - // Sur la page de détails, chercher le bouton d'édition - // Sur la page de détails, chercher le bouton d'édition - // Note: Le texte est "Modifier" en français, pas "Éditer" - const editButton = page.locator('button:has-text("Edit"), button:has-text("Éditer"), button:has-text("Modifier"), button[aria-label*="edit" i], button[aria-label*="modifier" i]').first(); - const moreButton = page.locator('button:has-text("More"), button:has-text("Actions"), button[aria-label*="more" i], button[aria-label*="actions" i]').first(); - - // Attendre que les actions soient chargées - await page.waitForSelector('[role="group"][aria-label="Actions de la playlist"]', { timeout: 10000 }).catch(() => console.warn('⚠️ Actions group not found')); - - const isEditVisible = await editButton.isVisible().catch(() => false); - const isMoreVisible = await moreButton.isVisible().catch(() => false); - - if (isEditVisible) { - console.log('🔍 Clicking edit button via dispatchEvent'); - // Utiliser dispatchEvent pour contourner l'overlay de la sidebar qui intercepte le click - await editButton.dispatchEvent('click'); - } else if (isMoreVisible) { - await expect(moreButton).toBeVisible({ timeout: 5000 }); - await moreButton.click(); - await page.waitForTimeout(500); - const editMenuItem = page.locator('[role="menuitem"]:has-text("Edit"), [role="menuitem"]:has-text("Éditer")').first(); - await expect(editMenuItem).toBeVisible({ timeout: 5000 }); - await editMenuItem.click(); - } else { - // Si pas de bouton d'édition visible, on est peut-être déjà sur la page de détails - // Chercher un formulaire d'édition ou un bouton pour ouvrir l'édition - console.warn('⚠️ [PLAYLISTS] Edit button not found, playlist may not be editable or UI changed'); - } - - // Attendre que la modal d'édition s'ouvre - await page.waitForSelector('[role="dialog"]', { timeout: 5000 }); - - // Modifier le nom - const updatedName = `Updated Playlist ${Date.now()}`; - // 🔴 FIX: Ajouter l'ID spécifique utilisé dans PlaylistActions (edit-title) - const nameField = page.locator('input[name="name"], input[name="title"], input#title, input#edit-title').first(); - await nameField.clear(); - await nameField.fill(updatedName); - - // Soumettre en cliquant sur "Enregistrer" (pas de balise form dans le dialog) - // await forceSubmitForm(page, 'form'); // Ne marche pas car pas de form - const saveButton = page.locator('[role="dialog"] button').filter({ hasText: /enregistrer/i }).first(); - await expect(saveButton).toBeVisible({ timeout: 5000 }); - await saveButton.click({ force: true }); - await waitForToast(page, 'success', 10000); - - // Retourner à la liste des playlists pour vérifier - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // 🔴 FIX: Utiliser getByText pour une recherche directe et fiable - // 🔴 FIX: Cibler le lien de la card pour la vérification - const updatedPlaylist = page.locator('a[href*="/playlists/"]').filter({ hasText: updatedName }).first(); - await expect(updatedPlaylist).toBeVisible({ timeout: 15000 }); - - console.log('✅ [PLAYLISTS] Playlist updated successfully'); - }); - - /** - * TEST 4: Ajouter une track à une playlist - */ - test('should add track to playlist', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Add track to playlist'); - - // Créer une playlist - await navigateViaHref(page, '/playlists', /\/playlists/); - await openModal(page, /create|créer|nouvelle/i); - - const playlistName = `Add Track Playlist ${Date.now()}`; - await fillField(page, 'input[name="name"], input[name="title"], input#title', playlistName); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - - // Attendre que la modal se ferme - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // 🔴 FIX: Recharger pour s'assurer que la playlist est créée avant de naviguer - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(1000); - - // Naviguer vers la bibliothèque pour trouver une track - await navigateViaHref(page, '/library', /\/library/); - - // Attendre que la page soit chargée - await page.waitForLoadState('domcontentloaded'); - await page.waitForTimeout(1000); - - // 🔴 FIX: La bibliothèque peut utiliser une table OU une grille de cards - // Attendre qu'au moins un élément de track soit visible (plus flexible) - try { - await waitForListLoaded(page, 1); - } catch { - // Si waitForListLoaded échoue, essayer de trouver directement une track - const trackElement = page.locator('tr, [role="row"], [role="listitem"], .track-card, [data-testid*="track"], [role="grid"] > *').first(); - await expect(trackElement).toBeVisible({ timeout: 10000 }); - } - - // 🔴 FIX: Trouver la première track avec un sélecteur générique (table OU grid) - // Essayer d'abord table row, puis grid item, puis n'importe quel élément contenant du texte de track - let firstTrack = page.locator('tr, [role="row"]').filter({ has: page.locator('td, [role="cell"]') }).first(); - if (!(await firstTrack.isVisible({ timeout: 2000 }).catch(() => false))) { - // Si pas de table, essayer grid ou card - firstTrack = page.locator('[role="grid"] > *, [role="listitem"], .track-card, [data-testid*="track"]').first(); - } - await expect(firstTrack).toBeVisible({ timeout: 10000 }); - - // Ouvrir le menu "Add to Playlist" - const addToPlaylistButton = firstTrack.locator('button:has-text("Add to playlist"), button:has-text("Ajouter à"), button[aria-label*="playlist" i]').first(); - const moreButton = firstTrack.locator('button:has-text("More"), button:has-text("Actions")').first(); - - const isAddVisible = await addToPlaylistButton.isVisible().catch(() => false); - const isMoreVisible = await moreButton.isVisible().catch(() => false); - - if (isAddVisible) { - await expect(addToPlaylistButton).toBeVisible({ timeout: 5000 }); - await addToPlaylistButton.click(); - } else if (isMoreVisible) { - await expect(moreButton).toBeVisible({ timeout: 5000 }); - await moreButton.click(); - await page.waitForTimeout(500); - const addMenuItem = page.locator('[role="menuitem"]:has-text("Add to playlist"), [role="menuitem"]:has-text("Ajouter")').first(); - await expect(addMenuItem).toBeVisible({ timeout: 5000 }); - await addMenuItem.click(); - } else { - console.warn('⚠️ [PLAYLISTS] Add to playlist button not found, skipping test'); - test.skip(); - return; - } - - // Sélectionner la playlist dans le menu/modal - await page.waitForTimeout(500); - const playlistOption = page.locator(`text=${playlistName}, [role="menuitem"]:has-text("${playlistName}")`).first(); - - const isPlaylistOptionVisible = await playlistOption.isVisible({ timeout: 5000 }).catch(() => false); - - if (isPlaylistOptionVisible) { - await expect(playlistOption).toBeVisible({ timeout: 5000 }); - await playlistOption.click(); - await waitForToast(page, 'success', 10000); - console.log('✅ [PLAYLISTS] Track added to playlist successfully'); - } else { - console.warn('⚠️ [PLAYLISTS] Playlist option not found in menu'); - } - }); - - /** - * TEST 5: Supprimer une playlist - */ - test('should delete playlist successfully', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Delete playlist'); - - // Créer une playlist à supprimer - await navigateViaHref(page, '/playlists', /\/playlists/); - await openModal(page, /create|créer|nouvelle/i); - - const playlistName = `Delete Playlist ${Date.now()}`; - await fillField(page, 'input[name="name"], input[name="title"], input#title', playlistName); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - - // Attendre que la modal se ferme - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // 🔴 FIX: Recharger la page pour forcer le rafraîchissement de la liste - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // 🔴 FIX: Utiliser directement getByText au lieu de waitForListLoaded - // Plus fiable car il cherche directement le texte, indépendamment de la structure UI - // 🔴 FIX: Cibler le lien de la card spécifiquement - const playlistCard = page.locator('a[href*="/playlists/"]').filter({ hasText: playlistName }).first(); - await expect(playlistCard).toBeVisible({ timeout: 15000 }); - - // 🔴 FIX: Naviguer manuellement vers la page de détails - const href = await playlistCard.getAttribute('href'); - if (!href) throw new Error('Playlist card has no href'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${href}`, { waitUntil: 'networkidle' }); - await page.waitForURL(/\/playlists\/[^/]+/, { timeout: 10000 }); - - // Sur la page de détails, chercher le bouton de suppression - const deleteButton = page.locator('button:has-text("Delete"), button:has-text("Supprimer"), button[aria-label*="delete" i], button[aria-label*="supprimer" i]').first(); - const moreButton = page.locator('button:has-text("More"), button:has-text("Actions"), button[aria-label*="more" i], button[aria-label*="actions" i]').first(); - - // Attendre que les actions soient chargées - await page.waitForSelector('[role="group"][aria-label="Actions de la playlist"]', { timeout: 10000 }).catch(() => console.warn('⚠️ Actions group not found')); - - const isDeleteVisible = await deleteButton.isVisible().catch(() => false); - const isMoreVisible = await moreButton.isVisible().catch(() => false); - - if (isDeleteVisible) { - await expect(deleteButton).toBeVisible({ timeout: 5000 }); - await deleteButton.click({ force: true }); - } else if (isMoreVisible) { - await expect(moreButton).toBeVisible({ timeout: 5000 }); - await moreButton.click(); - await page.waitForTimeout(500); - const deleteMenuItem = page.locator('[role="menuitem"]:has-text("Delete"), [role="menuitem"]:has-text("Supprimer")').first(); - await expect(deleteMenuItem).toBeVisible({ timeout: 5000 }); - await deleteMenuItem.click(); - } else { - // Fallback: icône de corbeille - const trashButton = page.locator('button svg.lucide-trash, button svg.fa-trash').first(); - if (await trashButton.isVisible({ timeout: 2000 }).catch(() => false)) { - await trashButton.click(); - } else { - console.warn('⚠️ [PLAYLISTS] Delete button not found, playlist may not be deletable or UI changed'); - } - } - - // Confirmer la suppression si modal de confirmation - await page.waitForTimeout(500); - // 🔴 FIX: Cibler le bouton DANS le dialog - const confirmButton = page.locator('[role="dialog"] button:has-text("Confirm"), [role="dialog"] button:has-text("Oui"), [role="dialog"] button:has-text("Supprimer")').first(); - const isConfirmVisible = await confirmButton.isVisible().catch(() => false); - - if (isConfirmVisible) { - await expect(confirmButton).toBeVisible({ timeout: 5000 }); - await confirmButton.click({ force: true }); - // 🔴 FIX: Attendre la confirmation de suppression avant de continuer - // Sinon la navigation manuelle suivante peut annuler la requête - await waitForToast(page, 'success', 10000); - } - - // Attendre que la navigation automatique se fasse (le composant redirige vers /playlists) - await page.waitForURL(/\/playlists$/, { timeout: 10000 }).catch(() => { - // Fallback si la redirection auto ne marche pas ou est lente - console.log('⚠️ [PLAYLISTS] Auto-redirect failed/slow, manual navigation'); - return page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'networkidle' }); - }); - - // Attendre le rechargement de la liste - await page.waitForTimeout(2000); - - // 🔴 FIX: Vérifier que la playlist supprimée n'apparaît plus dans la liste - // Utiliser getByText qui est plus fiable pour vérifier l'absence - // 🔴 FIX: Vérifier que la playlist supprimée n'apparaît plus dans la liste - const deletedPlaylistCard = page.locator('a[href*="/playlists/"]').filter({ hasText: playlistName }).first(); - await expect(deletedPlaylistCard).not.toBeVisible({ timeout: 15000 }); - - // Vérifier persistence (reload pour s'assurer que la suppression est persistée) - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(1000); - - // Ne pas utiliser waitForListLoaded ici car on ne sait pas combien de playlists restent - // Vérifier directement que la playlist supprimée n'est plus visible - const deletedPlaylist = page.getByText(playlistName); - await expect(deletedPlaylist).not.toBeVisible({ timeout: 10000 }); - - console.log('✅ [PLAYLISTS] Playlist deleted successfully'); - }); - - /** - * TEST 6: Playlist vide (sans tracks) - */ - test('should display empty state for new playlist', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Empty playlist state'); - - // Créer une playlist - await navigateViaHref(page, '/playlists', /\/playlists/); - await openModal(page, /create|créer|nouvelle/i); - - const playlistName = `Empty Playlist ${Date.now()}`; - await fillField(page, 'input[name="name"], input[name="title"], input#title', playlistName); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - - // Attendre que la modal se ferme - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // 🔴 FIX: Recharger la page pour forcer le rafraîchissement de la liste - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // 🔴 FIX: Utiliser directement getByText au lieu de waitForListLoaded - // Plus fiable car il cherche directement le texte, indépendamment de la structure UI - // 🔴 FIX: Naviguer manuellement vers la page de détails pour éviter les problèmes de clic/overlay - // Comme fait dans les autres tests (update/delete) - const playlistLink = page.locator('a[href*="/playlists/"]').filter({ hasText: playlistName }).first(); - const href = await playlistLink.getAttribute('href'); - if (!href) throw new Error('Playlist card has no href'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${href}`, { waitUntil: 'networkidle' }); - - // Attendre que la page de détails se charge - await page.waitForURL(/\/playlists\/[^/]+/, { timeout: 10000 }); - - - // Vérifier l'état vide - const emptyState = page.locator('text=/empty|vide|aucune track|no tracks/i').first(); - const isEmptyStateVisible = await emptyState.isVisible({ timeout: 5000 }).catch(() => false); - - if (isEmptyStateVisible) { - console.log('✅ [PLAYLISTS] Empty state displayed correctly'); - } else { - console.log('ℹ️ [PLAYLISTS] Empty state not explicitly shown (may be implicit)'); - } - }); - - /** - * TEST 7: Recherche de playlists - */ - test('should search playlists by name', async ({ page }) => { - console.log('🧪 [PLAYLISTS] Running: Search playlists'); - - // Créer plusieurs playlists - await navigateViaHref(page, '/playlists', /\/playlists/); - - const searchTerm = `SearchTest${Date.now()}`; - - // Créer playlist 1 - await openModal(page, /create|créer|nouvelle/i); - await fillField(page, 'input[name="name"], input[name="title"], input#title', `${searchTerm} Alpha`); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // Créer playlist 2 - await openModal(page, /create|créer|nouvelle/i); - await fillField(page, 'input[name="name"], input[name="title"], input#title', `${searchTerm} Beta`); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // Créer playlist 3 (différente) - const differentName = `Different ${Date.now()}`; - await openModal(page, /create|créer|nouvelle/i); - await fillField(page, 'input[name="name"], input[name="title"], input#title', differentName); - await forceSubmitForm(page, 'form'); - await waitForToast(page, 'success', 10000); - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }).catch(() => { }); - - // 🔴 FIX: Recharger la page pour forcer le rafraîchissement de la liste - await page.reload({ waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // 🔴 FIX: Vérifier directement que les playlists créées sont visibles - // Au lieu de compter les éléments, on vérifie directement les textes - await expect(page.getByText(`${searchTerm} Alpha`)).toBeVisible({ timeout: 10000 }); - await expect(page.getByText(`${searchTerm} Beta`)).toBeVisible({ timeout: 10000 }); - await expect(page.getByText(differentName)).toBeVisible({ timeout: 10000 }); - - // Chercher un champ de recherche - // Chercher un champ de recherche - // 🔴 FIX: Cibler spécifiquement la recherche de playlist (éviter la recherche globale) - const searchInput = page.locator('[data-testid="playlist-search"]').first(); - const isSearchVisible = await searchInput.isVisible({ timeout: 2000 }).catch(() => false); - - // Fallback: ancien sélecteur si data-testid pas encore déployé (ou autre input) - if (!isSearchVisible) { - const fallbackInput = page.locator('input[placeholder*="Search" i], input[placeholder*="Recherche" i], input[type="search"]').filter({ hasNot: page.locator('[aria-label="Global search"]') }).first(); - if (await fallbackInput.isVisible().catch(() => false)) { - // Mais attention, si c'est la recherche globale, ça ne marchera pas - console.warn('⚠️ Using fallback search selector, might be global search'); - // On continue quand même pour voir - } - } - - if (isSearchVisible) { - // Effectuer la recherche - await searchInput.fill(searchTerm); - await page.waitForTimeout(1000); // Attendre le debounce - - // 🔴 FIX: Utiliser getByText pour une recherche directe et fiable - const alphaPlaylist = page.getByText(`${searchTerm} Alpha`); - const betaPlaylist = page.getByText(`${searchTerm} Beta`); - // differentName est défini dans le scope ci-dessus - const differentPlaylist = page.getByText(differentName); - - await expect(alphaPlaylist).toBeVisible({ timeout: 5000 }); - await expect(betaPlaylist).toBeVisible({ timeout: 5000 }); - await expect(differentPlaylist).not.toBeVisible(); - - console.log('✅ [PLAYLISTS] Search functionality works correctly'); - } else { - console.log('ℹ️ [PLAYLISTS] Search functionality not implemented yet'); - } - }); - - /** - * FINAL VERIFICATIONS - */ - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [PLAYLISTS] === Final Verifications ==='); - - if (consoleErrors.length > 0) { - console.log(`🔴 [PLAYLISTS] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((error) => { - console.log(` - ${error}`); - }); - } else { - console.log('✅ [PLAYLISTS] No console errors'); - } - - if (networkErrors.length > 0) { - console.log(`🔴 [PLAYLISTS] Network errors (${networkErrors.length}):`); - networkErrors.forEach((error) => { - console.log(` - ${error.method} ${error.url}: ${error.status}`); - }); - } else { - console.log('✅ [PLAYLISTS] No network errors'); - } - }); -}); diff --git a/apps/web/e2e/tests/profile.spec.ts b/apps/web/e2e/tests/profile.spec.ts deleted file mode 100644 index 8dc8aeb61..000000000 --- a/apps/web/e2e/tests/profile.spec.ts +++ /dev/null @@ -1,588 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - forceSubmitForm, - fillField, - safeClick, - navigateViaSidebar, - setupErrorCapture, - waitForToast, -} from '../utils/test-helpers'; - -/** - * Profile E2E Test Suite - * - * Teste la gestion du profil utilisateur : - * - Affichage du profil - * - Modification des informations personnelles (username, bio, etc.) - * - Changement de mot de passe - * - Upload d'avatar - * - Validation des champs - */ - -test.describe('User Profile Management', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - // Augmenter le timeout global pour ces tests (certains prennent du temps) - test.describe.configure({ timeout: 60000 }); - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - - // 1. Login avant chaque test (nous laisse sur /dashboard si déjà connecté) - await loginAsUser(page); - - // 2. CORRECTION : Forcer la navigation vers le profil - console.log('🧭 [NAVIGATION] Going to profile page...'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`, { waitUntil: 'networkidle' }); - await page.waitForLoadState('networkidle'); - }); - - /** - * TEST 1: Afficher le profil utilisateur - */ - test('should display user profile information', async ({ page }) => { - console.log('🧪 [PROFILE] Running: Display profile'); - - // Naviguer vers la page de profil (via sidebar ou menu utilisateur) - // Essayer plusieurs méthodes car la navigation peut varier selon l'UI - - // Méthode 1: Via sidebar - const profileLinkSidebar = page.locator('[role="menuitem"]:has-text("Profil"), [role="menuitem"]:has-text("Profile")').first(); - const isSidebarLinkVisible = await profileLinkSidebar.isVisible({ timeout: 3000 }).catch(() => false); - - if (isSidebarLinkVisible) { - await expect(profileLinkSidebar).toBeVisible({ timeout: 5000 }); - await profileLinkSidebar.click(); - } else { - // Méthode 2: Via menu utilisateur (Avatar dropdown) - const userMenu = page.locator('[data-testid="user-menu"], button[aria-label*="user" i], button[aria-label*="profile" i]').first(); - const isUserMenuVisible = await userMenu.isVisible().catch(() => false); - - if (isUserMenuVisible) { - await expect(userMenu).toBeVisible({ timeout: 5000 }); - await userMenu.click(); - await page.waitForTimeout(500); - await page.locator('[role="menuitem"]:has-text("Profil"), [role="menuitem"]:has-text("Profile")').first().click(); - } else { - // Méthode 3: Navigation directe - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - } - } - - // Attendre que la page se charge - await page.waitForURL(/\/profile|\/settings/, { timeout: 10000 }).catch(() => { - console.warn('⚠️ [PROFILE] URL did not change to profile page'); - }); - - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { - console.warn('⚠️ [PROFILE] Timeout on networkidle, continuing...'); - }); - - // Vérifier que le titre de la page est visible (peut être h1, h2, ou dans un CardTitle) - // Le ProfileForm utilise CardTitle avec t('profile.title') - const pageTitle = page.locator( - 'h1:has-text("Profil"), h1:has-text("Profile"), h2:has-text("Profil"), h2:has-text("Profile"), [class*="CardTitle"], [class*="card-title"]' - ).first(); - // Si le titre n'est pas trouvé, vérifier au moins qu'on est sur la bonne page - const titleVisible = await pageTitle.isVisible({ timeout: 5000 }).catch(() => false); - if (!titleVisible) { - // Vérifier qu'on est bien sur /profile - const currentUrl = page.url(); - expect(currentUrl).toMatch(/\/profile/); - console.warn('⚠️ [PROFILE] Page title not found but URL is correct, continuing...'); - } else { - await expect(pageTitle).toBeVisible({ timeout: 10000 }); - } - - // Vérifier que les informations utilisateur sont affichées - // Le champ peut être un input (mode édition) ou un élément d'affichage (mode lecture) - const usernameDisplay = page.locator( - 'input#username, input[name="username"], [data-testid="username"], label:has-text("Username") + * input, label:has-text("Nom d\'utilisateur") + * input' - ).first(); - await expect(usernameDisplay).toBeVisible({ timeout: 15000 }); - - console.log('✅ [PROFILE] Profile page displayed successfully'); - }); - - /** - * TEST 2: Modifier le username - */ - test('should update username successfully', async ({ page }) => { - test.setTimeout(60000); // 60 secondes pour ce test spécifique - console.log('🧪 [PROFILE] Running: Update username'); - - // Naviguer vers le profil - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que le formulaire soit visible - // Le champ username utilise id="username" dans ProfileForm - const usernameField = page.locator('input#username, input[name="username"]').first(); - await expect(usernameField).toBeVisible({ timeout: 15000 }); - - // 🔴 FIX: Attendre que le champ soit peuplé avec les données de l'utilisateur - // React doit finir de charger les données depuis l'API avant qu'on puisse les modifier - console.log('⏳ [PROFILE] Waiting for username field to be populated...'); - await page.waitForFunction( - (selector) => { - const input = document.querySelector(selector) as HTMLInputElement; - return input && input.value && input.value.trim().length > 0; - }, - 'input#username, input[name="username"]', - { timeout: 15000 } - ).catch(() => { - console.warn('⚠️ [PROFILE] Username field not populated, continuing anyway...'); - }); - - // Si le champ est disabled (mode lecture), cliquer sur le bouton Edit - const isDisabled = await usernameField.isDisabled().catch(() => false); - if (isDisabled) { - const editButton = page.locator('button:has-text("Edit"), button:has-text("Modifier"), button:has-text("profile.edit")').first(); - if (await editButton.isVisible({ timeout: 5000 }).catch(() => false)) { - await expect(editButton).toBeVisible({ timeout: 5000 }); - await editButton.click(); - await page.waitForTimeout(500); // Attendre que le mode édition s'active - // Re-vérifier que le champ est maintenant éditable - await expect(usernameField).toBeEnabled({ timeout: 5000 }); - } - } - - // Modifier le username - const newUsername = `testuser_${Date.now()}`; - await usernameField.clear(); - await usernameField.fill(newUsername); - - // Soumettre le formulaire - const submitButton = page.locator('button:has-text("Save"), button:has-text("Enregistrer"), button[type="submit"]').first(); - await expect(submitButton).toBeVisible({ timeout: 5000 }); - - // Attendre l'appel API - const updatePromise = page.waitForResponse( - (response) => - response.url().includes('/users') && - response.request().method() === 'PUT' && - response.status() < 500, - { timeout: 15000 } - ); - - await submitButton.click(); - - // Attendre la réponse - try { - const response = await updatePromise; - const status = response.status(); - console.log(`📡 [PROFILE] Update response: ${status}`); - - if (status === 200 || status === 204) { - await waitForToast(page, 'success', 10000); - console.log('✅ [PROFILE] Username updated successfully'); - } else { - console.warn(`⚠️ [PROFILE] Update failed with status ${status}`); - } - } catch (error) { - console.warn('⚠️ [PROFILE] Update request timeout'); - } - - // Vérifier que le nouveau username est affiché - // 🔴 FIX: Vérifier que la page est toujours ouverte avant de faire le reload - if (page.isClosed()) { - console.warn('⚠️ [PROFILE] Page was closed, cannot verify username persistence'); - return; - } - - try { - await page.reload({ waitUntil: 'domcontentloaded', timeout: 30000 }); - await page.waitForLoadState('networkidle', { timeout: 30000 }); - - // 🔴 FIX: Attendre que le champ soit peuplé après le reload - const updatedUsernameField = page.locator('input[name="username"], input#username').first(); - await expect(updatedUsernameField).toBeVisible({ timeout: 15000 }); - - // Attendre que le champ soit peuplé avec les données - await page.waitForFunction( - (selector) => { - const input = document.querySelector(selector) as HTMLInputElement; - return input && input.value && input.value.trim().length > 0; - }, - 'input[name="username"], input#username', - { timeout: 15000 } - ).catch(() => { - console.warn('⚠️ [PROFILE] Username field not populated after reload, continuing...'); - }); - - const currentValue = await updatedUsernameField.inputValue(); - expect(currentValue).toBe(newUsername); - - console.log('✅ [PROFILE] Username persisted after reload'); - } catch (error) { - console.warn('⚠️ [PROFILE] Reload failed or timeout, but update was successful (check logs)'); - // Ne pas faire échouer le test car l'update a réussi (status 200/204) - } - }); - - /** - * TEST 3: Modifier la bio - */ - test('should update bio successfully', async ({ page }) => { - console.log('🧪 [PROFILE] Running: Update bio'); - - // Naviguer vers le profil - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('domcontentloaded'); - - // Le champ bio utilise id="bio" dans ProfileForm (c'est un Input, pas un textarea) - const bioField = page.locator('input#bio, textarea#bio, [id="bio"]').first(); - - // Vérifier si le champ existe - const bioExists = await bioField.isVisible({ timeout: 5000 }).catch(() => false); - - if (!bioExists) { - console.log('ℹ️ [PROFILE] Bio field not found, skipping test'); - test.skip(); - return; - } - - // Si disabled, activer le mode édition - const isDisabled = await bioField.isDisabled().catch(() => false); - if (isDisabled) { - const editButton = page.locator('button:has-text("Edit"), button:has-text("Modifier")').first(); - if (await editButton.isVisible({ timeout: 5000 }).catch(() => false)) { - await editButton.click(); - await page.waitForTimeout(500); - await expect(bioField).toBeEnabled({ timeout: 5000 }); - } - } - - // Modifier la bio - const newBio = `This is a test bio updated at ${new Date().toISOString()}`; - await bioField.clear(); - await bioField.fill(newBio); - - // Soumettre le formulaire - const submitButton = page.locator('button:has-text("Save"), button:has-text("Enregistrer"), button[type="submit"]').first(); - await submitButton.click(); - - // Attendre le succès - await waitForToast(page, 'success', 10000); - - // Vérifier la persistence - await page.reload({ waitUntil: 'domcontentloaded' }); - const updatedBioField = page.locator('textarea[name="bio"], textarea#bio').first(); - const currentValue = await updatedBioField.inputValue(); - expect(currentValue).toBe(newBio); - - console.log('✅ [PROFILE] Bio updated successfully'); - }); - - /** - * TEST 4: Changer le mot de passe - */ - test('should change password successfully', async ({ page }) => { - console.log('🧪 [PROFILE] Running: Change password'); - - // Naviguer vers le profil ou settings - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('domcontentloaded'); - - // Chercher un lien/bouton "Change Password" ou "Security" - const changePasswordButton = page.locator('button:has-text("Change password"), button:has-text("Changer"), a:has-text("Security"), a:has-text("Sécurité")').first(); - const isChangePasswordVisible = await changePasswordButton.isVisible({ timeout: 5000 }).catch(() => false); - - if (!isChangePasswordVisible) { - console.log('ℹ️ [PROFILE] Change password section not found, skipping test'); - test.skip(); - return; - } - - await changePasswordButton.click(); - await page.waitForTimeout(500); - - // Remplir le formulaire de changement de mot de passe - const currentPasswordField = page.locator('input[name="currentPassword"], input[name="current_password"], input#currentPassword').first(); - const newPasswordField = page.locator('input[name="newPassword"], input[name="new_password"], input#newPassword').first(); - const confirmPasswordField = page.locator('input[name="confirmPassword"], input[name="confirm_password"], input#confirmPassword').first(); - - const areFieldsVisible = await currentPasswordField.isVisible({ timeout: 5000 }).catch(() => false); - - if (!areFieldsVisible) { - console.log('ℹ️ [PROFILE] Password fields not found, skipping test'); - test.skip(); - return; - } - - // Remplir avec le mot de passe actuel et un nouveau - await currentPasswordField.fill('password123'); // Mot de passe actuel du test user - const newPassword = `NewPass${Date.now()}!`; - await newPasswordField.fill(newPassword); - await confirmPasswordField.fill(newPassword); - - // Soumettre - const submitButton = page.locator('button:has-text("Change"), button:has-text("Update"), button[type="submit"]').first(); - await submitButton.click(); - - // Attendre le résultat - try { - await waitForToast(page, 'success', 10000); - console.log('✅ [PROFILE] Password changed successfully'); - - // Note: Dans un vrai test, on devrait se déconnecter et se reconnecter avec le nouveau mot de passe - // Mais pour éviter de casser les autres tests, on restaure l'ancien mot de passe - - await page.waitForTimeout(1000); - - // Restaurer l'ancien mot de passe - await currentPasswordField.fill(newPassword); - await newPasswordField.fill('password123'); - await confirmPasswordField.fill('password123'); - await submitButton.click(); - await page.waitForTimeout(2000); - } catch (error) { - console.warn('⚠️ [PROFILE] Password change failed or timed out'); - } - }); - - /** - * TEST 5: Upload d'avatar - */ - test('should upload profile avatar', async ({ page }) => { - console.log('🧪 [PROFILE] Running: Upload avatar'); - - // Naviguer vers le profil - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('domcontentloaded'); - - // Chercher l'input file pour l'avatar - const avatarInput = page.locator('input[type="file"][accept*="image"], input[name="avatar"]').first(); - const isAvatarInputVisible = await avatarInput.isVisible({ timeout: 5000 }).catch(() => false); - - if (!isAvatarInputVisible) { - // Essayer de cliquer sur l'avatar pour révéler l'input - const avatarContainer = page.locator('[data-testid="avatar"], img[alt*="avatar" i], button:has-text("Upload")').first(); - const isAvatarContainerVisible = await avatarContainer.isVisible().catch(() => false); - - if (isAvatarContainerVisible) { - await expect(avatarContainer).toBeVisible({ timeout: 5000 }); - await avatarContainer.click(); - await page.waitForTimeout(500); - } else { - console.log('ℹ️ [PROFILE] Avatar upload not found, skipping test'); - test.skip(); - return; - } - } - - // Créer une image de test (1x1 PNG transparent) - const imageBuffer = Buffer.from( - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', - 'base64' - ); - - // Upload l'image - const fileInputFinal = page.locator('input[type="file"][accept*="image"]').first(); - await fileInputFinal.setInputFiles({ - name: 'avatar.png', - mimeType: 'image/png', - buffer: imageBuffer, - }); - - // Attendre l'upload - await page.waitForTimeout(2000); - - // Vérifier le succès (toast ou preview) - const successVisible = await page - .locator('text=/uploaded|success|succès/i') - .isVisible({ timeout: 5000 }) - .catch(() => false); - - if (successVisible) { - console.log('✅ [PROFILE] Avatar uploaded successfully'); - } else { - console.log('ℹ️ [PROFILE] Avatar upload completed (no explicit success message)'); - } - }); - - /** - * TEST 6: Validation des champs (username trop court) - */ - test('should validate username length', async ({ page }) => { - test.setTimeout(60000); // 60 secondes pour ce test spécifique - console.log('🧪 [PROFILE] Running: Username validation'); - - // Naviguer vers le profil - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que le champ username soit visible - const usernameField = page.locator('input#username, input[name="username"]').first(); - await expect(usernameField).toBeVisible({ timeout: 15000 }); - - // Si disabled, activer le mode édition - const isDisabled = await usernameField.isDisabled().catch(() => false); - if (isDisabled) { - const editButton = page.locator('button:has-text("Edit"), button:has-text("Modifier")').first(); - if (await editButton.isVisible({ timeout: 5000 }).catch(() => false)) { - await editButton.click(); - await page.waitForTimeout(500); - await expect(usernameField).toBeEnabled({ timeout: 5000 }); - } - } - - // Essayer un username trop court (< 3 caractères) - await usernameField.clear(); - await usernameField.fill('ab'); - - // 🔴 FIX: Forcer la validation React en déclenchant un événement blur - // Cela garantit que React Hook Form met à jour l'état de validation - await usernameField.blur(); - await page.waitForTimeout(500); // Attendre que React mette à jour l'état - - // 🔴 FIX: Vérifier la validation en cherchant plusieurs indicateurs - // 1. Vérifier les messages d'erreur visibles (React Hook Form / Zod) - const errorMessageSelectors = [ - 'p.text-destructive', - 'p.text-red-500', - 'p.text-red-600', - '[role="alert"]', - '.text-error', - '.error-message', - 'text=/trop court|too short|minimum|at least|caractères|characters/i', - ]; - - let validationDetected = false; - - // Chercher un message d'erreur visible - for (const selector of errorMessageSelectors) { - const errorElement = page.locator(selector).first(); - const isVisible = await errorElement.isVisible({ timeout: 2000 }).catch(() => false); - if (isVisible) { - const errorText = await errorElement.textContent().catch(() => ''); - if (errorText && (errorText.toLowerCase().includes('short') || - errorText.toLowerCase().includes('court') || - errorText.toLowerCase().includes('minimum') || - errorText.toLowerCase().includes('caractère'))) { - console.log(`✅ [PROFILE] Validation error found: ${errorText}`); - validationDetected = true; - break; - } - } - } - - // 2. Vérifier l'attribut aria-invalid - if (!validationDetected) { - const ariaInvalid = await usernameField.getAttribute('aria-invalid'); - if (ariaInvalid === 'true') { - console.log('✅ [PROFILE] Validation detected via aria-invalid'); - validationDetected = true; - } - } - - // 3. Vérifier si le bouton submit est désactivé (indicateur de validation) - if (!validationDetected) { - const submitButton = page.locator('button:has-text("Save"), button:has-text("Enregistrer"), button[type="submit"]').first(); - const isDisabled = await submitButton.isDisabled().catch(() => false); - if (isDisabled) { - console.log('✅ [PROFILE] Validation detected via disabled submit button'); - validationDetected = true; - } - } - - // 4. Essayer de soumettre et vérifier qu'une erreur apparaît - if (!validationDetected) { - const submitButton = page.locator('button:has-text("Save"), button:has-text("Enregistrer"), button[type="submit"]').first(); - await submitButton.click(); - await page.waitForTimeout(500); - - // Vérifier qu'un message d'erreur apparaît après la tentative de soumission - const errorAfterSubmit = page.locator('text=/trop court|too short|minimum|at least|caractères|characters|erreur|error/i, [role="alert"]').first(); - const isErrorAfterSubmit = await errorAfterSubmit.isVisible({ timeout: 3000 }).catch(() => false); - if (isErrorAfterSubmit) { - console.log('✅ [PROFILE] Validation error shown after submit attempt'); - validationDetected = true; - } - } - - // 5. Fallback: Vérifier la validation HTML5 native (si rien d'autre n'a fonctionné) - if (!validationDetected) { - const isInvalid = await usernameField.evaluate((el: HTMLInputElement) => !el.validity.valid); - if (isInvalid) { - console.log('✅ [PROFILE] HTML5 validation working (fallback)'); - validationDetected = true; - } - } - - // Assertion finale - expect(validationDetected).toBeTruthy(); - console.log('✅ [PROFILE] Username validation working correctly'); - }); - - /** - * TEST 7: Afficher les informations du compte (email, date de création) - */ - test('should display account information', async ({ page }) => { - console.log('🧪 [PROFILE] Running: Display account info'); - - // Naviguer vers le profil - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('domcontentloaded'); - - // Vérifier que l'email est affiché (généralement en lecture seule) - const emailDisplay = page.locator('input[name="email"], input[type="email"], text=/email/i').first(); - const isEmailVisible = await emailDisplay.isVisible({ timeout: 5000 }).catch(() => false); - - if (isEmailVisible) { - console.log('✅ [PROFILE] Email displayed'); - } - - // Vérifier que d'autres informations du compte sont présentes - // (date de création, rôle, etc.) - const accountInfo = page.locator('text=/member since|membre depuis|created|créé/i').first(); - const isAccountInfoVisible = await accountInfo.isVisible({ timeout: 5000 }).catch(() => false); - - if (isAccountInfoVisible) { - console.log('✅ [PROFILE] Account information displayed'); - } else { - console.log('ℹ️ [PROFILE] Additional account info not displayed'); - } - }); - - /** - * TEST 8: Lien vers les paramètres avancés - */ - // TEST 8: Lien vers les paramètres avancés - SUPPRIMÉ car la fonctionnalité n'existe pas - /* - test('should navigate to advanced settings', async ({ page }) => { - // ... skipped ... - }); - */ - - /** - * FINAL VERIFICATIONS - */ - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [PROFILE] === Final Verifications ==='); - - if (consoleErrors.length > 0) { - console.log(`🔴 [PROFILE] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((error) => { - console.log(` - ${error}`); - }); - } else { - console.log('✅ [PROFILE] No console errors'); - } - - if (networkErrors.length > 0) { - console.log(`🔴 [PROFILE] Network errors (${networkErrors.length}):`); - networkErrors.forEach((error) => { - console.log(` - ${error.method} ${error.url}: ${error.status}`); - }); - } else { - console.log('✅ [PROFILE] No network errors'); - } - }); -}); diff --git a/apps/web/e2e/tests/purchase.spec.ts b/apps/web/e2e/tests/purchase.spec.ts deleted file mode 100644 index b4e0311a8..000000000 --- a/apps/web/e2e/tests/purchase.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - setupErrorCapture, - waitForToast, -} from '../utils/test-helpers'; - -/** - * Purchase E2E Test Suite (Audit 2.10) - * - * Couvre le flow critique d'achat : - * - Marketplace → Add to cart → Checkout → Success - * - Cart empty state - */ - -test.describe('Purchase Flow', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - test('should add product to cart and checkout successfully', async ({ page }) => { - console.log('🧪 [PURCHASE] Running: Add to cart and checkout'); - - await loginAsUser(page); - await page.waitForTimeout(1000); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/marketplace`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {}); - - const productCard = page.locator('article[aria-label^="Product:"]').first(); - await expect(productCard).toBeVisible({ timeout: 10000 }); - - await productCard.hover(); - await page.waitForTimeout(300); - - const addToCartButton = page.locator('button:has-text("Add to Cart")').first(); - await expect(addToCartButton).toBeVisible({ timeout: 5000 }); - await addToCartButton.click(); - - await page.waitForTimeout(1500); - - const cartButton = page.locator('button:has-text("Cart")').first(); - await expect(cartButton).toBeVisible({ timeout: 5000 }); - await cartButton.click(); - - const cartDialog = page.locator('[role="dialog"]').filter({ hasText: 'Shopping Cart' }); - await expect(cartDialog).toBeVisible({ timeout: 5000 }); - - const checkoutButton = cartDialog.locator('button:has-text("Checkout")'); - await expect(checkoutButton).toBeVisible({ timeout: 3000 }); - await checkoutButton.click(); - - const successToast = await waitForToast(page, 'success', 15000); - expect(successToast.toLowerCase()).toMatch(/order|success|placed/); - - console.log('✅ [PURCHASE] Checkout successful'); - }); - - test('should show cart empty message when no items', async ({ page }) => { - console.log('🧪 [PURCHASE] Running: Cart empty state'); - - await loginAsUser(page); - await page.waitForTimeout(1000); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/marketplace`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {}); - - const cartButton = page.locator('button:has-text("Cart")').first(); - await expect(cartButton).toBeVisible({ timeout: 5000 }); - await cartButton.click(); - - const cartDialog = page.locator('[role="dialog"]').filter({ hasText: 'Shopping Cart' }); - await expect(cartDialog).toBeVisible({ timeout: 5000 }); - - const emptyMessage = cartDialog.locator('text=/cart is empty|your cart is empty/i'); - await expect(emptyMessage).toBeVisible({ timeout: 5000 }); - - console.log('✅ [PURCHASE] Cart empty message displayed'); - }); - - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [PURCHASE] === Final Verifications ==='); - if (consoleErrors.length > 0) { - console.log(`🔴 [PURCHASE] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((e) => console.log(` - ${e}`)); - } - if (networkErrors.length > 0) { - console.log(`🔴 [PURCHASE] Network errors (${networkErrors.length}):`); - networkErrors.forEach((e) => console.log(` - ${e.method} ${e.url}: ${e.status}`)); - } - }); -}); diff --git a/apps/web/e2e/tests/queue.spec.ts b/apps/web/e2e/tests/queue.spec.ts deleted file mode 100644 index 64dee7020..000000000 --- a/apps/web/e2e/tests/queue.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Queue E2E Tests (v0.102) - * - * Parcours : navigation /queue, affichage queue vide ou pleine, - * présence des actions Clear et Save Queue. - */ - -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - TEST_USERS, - loginAsUser, - setupErrorCapture, -} from '../utils/test-helpers'; - -test.describe('Queue Flow', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test.beforeEach(async ({ page }) => { - setupErrorCapture(page); - }); - - test('should show queue page with empty state or tracks', async ({ page }) => { - test.setTimeout(45000); - - await loginAsUser(page, TEST_USERS.default.email, TEST_USERS.default.password); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/queue`, { - waitUntil: 'domcontentloaded', - }); - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - - await expect(page).toHaveURL(/\/queue/); - - const heading = page.getByRole('heading', { name: /play queue/i }); - await expect(heading).toBeVisible({ timeout: 8000 }); - - const emptyState = page.getByText(/nothing in your queue|start playing music/i); - const saveButton = page.getByRole('button', { name: /save queue/i }); - const clearButton = page.getByRole('button', { name: /clear/i }); - const hasEmptyOrActions = - (await emptyState.isVisible({ timeout: 3000 }).catch(() => false)) || - (await saveButton.isVisible({ timeout: 3000 }).catch(() => false)) || - (await clearButton.isVisible({ timeout: 3000 }).catch(() => false)); - - expect(hasEmptyOrActions || (await heading.isVisible())).toBe(true); - }); -}); diff --git a/apps/web/e2e/tests/search.spec.ts b/apps/web/e2e/tests/search.spec.ts deleted file mode 100644 index 6b1330fe3..000000000 --- a/apps/web/e2e/tests/search.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Search E2E Tests - * - * Parcours critique : aller sur /search, saisir une requête, vérifier que des résultats - * (tracks/playlists) s'affichent ou que l'état vide est affiché. - */ - -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - TEST_USERS, - loginAsUser, - fillField, - forceSubmitForm, - setupErrorCapture, -} from '../utils/test-helpers'; - -test.describe('Search Flow', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - test('should show search page and display results or empty state', async ({ page }) => { - test.setTimeout(60000); - - await loginAsUser(page, TEST_USERS.default.email, TEST_USERS.default.password); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/search`, { waitUntil: 'domcontentloaded' }); - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {}); - - await expect(page).toHaveURL(/\/search/); - - const searchInput = page.locator( - 'input[type="search"], input[placeholder*="Search" i], input[placeholder*="Recherche" i], input[name="q"]' - ).first(); - await expect(searchInput).toBeVisible({ timeout: 10000 }); - - await searchInput.fill('test'); - await page.waitForTimeout(800); - - const resultsArea = page.locator('[data-testid="search-results"], [aria-label*="search" i], .search-results, main').first(); - await expect(resultsArea).toBeVisible({ timeout: 10000 }); - - const noResults = page.getByText(/no results|aucun résultat|no tracks|aucun track/i); - const hasResults = page.locator('a[href*="/tracks/"], [data-testid="track-card"], .track-card').first(); - const hasResultsOrEmpty = await noResults.isVisible({ timeout: 2000 }).catch(() => false) - || await hasResults.isVisible({ timeout: 2000 }).catch(() => false); - expect(hasResultsOrEmpty || (await resultsArea.isVisible())).toBe(true); - }); -}); diff --git a/apps/web/e2e/tests/smoke-post-deploy.spec.ts b/apps/web/e2e/tests/smoke-post-deploy.spec.ts deleted file mode 100644 index d2cbced6e..000000000 --- a/apps/web/e2e/tests/smoke-post-deploy.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Post-deployment smoke tests. - * Run against a deployed environment (staging/production) via PLAYWRIGHT_BASE_URL. - * Does NOT start the dev server - expects the target to be already running. - * - * Usage: - * PLAYWRIGHT_BASE_URL=https://staging.veza.com npx playwright test e2e/tests/smoke-post-deploy.spec.ts - * VITE_FRONTEND_URL=https://app.veza.com npx playwright test e2e/tests/smoke-post-deploy.spec.ts - */ - -import { test, expect } from '@playwright/test'; - -const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || process.env.VITE_FRONTEND_URL || 'http://localhost:5173'; - -test.describe('Post-deploy smoke checks', () => { - test('homepage loads', async ({ page }) => { - const response = await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 15000 }); - expect(response?.status()).toBeLessThan(500); - }); - - test('login page loads', async ({ page }) => { - const response = await page.goto(`${BASE_URL}/login`, { waitUntil: 'domcontentloaded', timeout: 15000 }); - expect(response?.status()).toBeLessThan(500); - }); - - test('API health check', async ({ request }) => { - const origin = new URL(BASE_URL).origin; - const apiUrl = `${origin}/api/v1/health`; - try { - const response = await request.get(apiUrl, { timeout: 10000 }); - expect(response.status()).toBeLessThan(500); - } catch { - test.skip(true, 'API health endpoint may not be reachable from this context'); - } - }); -}); diff --git a/apps/web/e2e/tests/smoke.spec.ts b/apps/web/e2e/tests/smoke.spec.ts deleted file mode 100644 index 86f4fcfa7..000000000 --- a/apps/web/e2e/tests/smoke.spec.ts +++ /dev/null @@ -1,372 +0,0 @@ -/** - * Critical User Flows E2E Tests - * FE-TEST-012: Test login, upload, playlist creation end-to-end - * - * This test suite covers the most critical user journeys: - * 1. User login flow - * 2. Track upload flow - * 3. Playlist creation flow - * - * These tests ensure that the core functionality works together seamlessly. - */ - - -import { test, expect } from '@playwright/test'; -import { - TEST_CONFIG, - TEST_USERS, - forceSubmitForm, - fillField, - waitForToast, - setupErrorCapture, - openModal, -} from '../utils/test-helpers'; -import { createMockMP3Buffer } from '../fixtures/file-helpers'; - -test.describe('Critical User Flows - End-to-End', () => { - // Reset storage state for these tests to ensure we start unauthenticated - test.use({ storageState: { cookies: [], origins: [] } }); - - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - /** - * CRITICAL FLOW 1: Complete user journey from login to playlist creation - * - * This test simulates a real user scenario: - * 1. User logs in - * 2. User uploads a track - * 3. User creates a playlist - * 4. User adds the uploaded track to the playlist - */ - test('Complete user journey: Login → Upload → Create Playlist → Add Track', async ({ page }) => { - test.setTimeout(180000); // 3 minutes for complete flow - - console.log('🚀 [CRITICAL FLOW] Starting complete user journey test...'); - - // ========== STEP 1: LOGIN ========== - console.log('📝 [CRITICAL FLOW] Step 1: User login...'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - - // Wait for form to be ready (align with auth.spec.ts) - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { }); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('input[type="email"], input[name="email"]').first()).toBeVisible({ timeout: 5000 }); - await page.waitForTimeout(500); - - // Fill login form - await fillField( - page, - 'input[type="email"], input[name="email"]', - TEST_USERS.default.email - ); - await fillField( - page, - 'input[type="password"], input[name="password"]', - TEST_USERS.default.password - ); - - // Submit form and wait for navigation - const navigationPromise = page.waitForURL( - (url) => url.pathname === '/dashboard' || url.pathname === '/', - { timeout: 15000 } - ); - - await forceSubmitForm(page, 'form'); - await navigationPromise; - - // Verify user is authenticated - await expect(page).toHaveURL(/\/(dashboard|$)/); - await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({ - timeout: 10000, - }); - - // Wait for auth state to be persisted - await page.waitForTimeout(1000); - - const isAuthenticated = await page.evaluate(() => { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } - } catch { - return false; - } - return false; - }); - expect(isAuthenticated).toBe(true); - console.log('✅ [CRITICAL FLOW] Step 1: Login successful'); - - // ========== STEP 2: UPLOAD TRACK ========== - console.log('📤 [CRITICAL FLOW] Step 2: Uploading track...'); - - // Navigate to library - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [CRITICAL FLOW] Timeout on networkidle, continuing...'); - }); - - // Open upload modal - await openModal(page, /upload/i); - - // Select and upload file - const fileInput = page.locator('input[type="file"][accept*="audio"]').first(); - await expect(fileInput).toBeAttached({ timeout: 5000 }); - - const validMp3Buffer = createMockMP3Buffer(); - await fileInput.setInputFiles({ - name: 'critical-flow-test.mp3', - mimeType: 'audio/mpeg', - buffer: validMp3Buffer, - }); - - console.log('✅ [CRITICAL FLOW] File selected'); - await page.waitForTimeout(1000); - - // Check for rejection errors - const errorMessage = page.locator('[data-testid="upload-error"], [role="alert"]:has-text("Format")').first(); - const hasRejectionError = await errorMessage.isVisible().catch(() => false); - - if (hasRejectionError) { - const errorText = await errorMessage.textContent(); - throw new Error(`File was rejected: ${errorText}`); - } - - // Fill metadata - await fillField(page, 'input[id="title"], input[name="title"]', 'Critical Flow Test Track'); - await fillField(page, 'input[id="artist"], input[name="artist"]', 'Test Artist'); - - // Submit upload - const uploadButton = page.locator('button:has-text("Upload"), button:has-text("Envoyer"), button[type="submit"]').first(); - await expect(uploadButton).toBeVisible({ timeout: 5000 }); - await uploadButton.click(); - - // Wait for upload success - await waitForToast(page, /success|succès|uploaded|téléchargé/i, { timeout: 60000 }); - console.log('✅ [CRITICAL FLOW] Step 2: Track uploaded successfully'); - - // Close modal if still open - const closeButton = page.locator('button[aria-label*="close"], button[aria-label*="fermer"]').first(); - if (await closeButton.isVisible().catch(() => false)) { - await expect(closeButton).toBeVisible({ timeout: 3000 }); - await closeButton.click(); - await page.waitForTimeout(500); - } - - // ========== STEP 3: CREATE PLAYLIST ========== - console.log('📋 [CRITICAL FLOW] Step 3: Creating playlist...'); - - // Navigate to playlists - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'domcontentloaded' }); - await page.waitForTimeout(500); - await page.waitForURL(/\/playlists/, { timeout: 15000 }).catch(() => { }); - - // Wait for page to load - try { - await Promise.race([ - page.locator('h1:has-text("Playlist"), h1:has-text("Playlists")').first().waitFor({ state: 'visible', timeout: 10000 }), - page.locator('[data-testid="playlists-page"]').first().waitFor({ state: 'visible', timeout: 10000 }), - ]); - } catch { - console.warn('⚠️ [CRITICAL FLOW] Page load check timeout, continuing...'); - } - - // Open create playlist modal - await openModal(page, /create|créer|nouvelle/i); - - // Fill playlist form - await fillField( - page, - 'input[id="title"], input[name="title"]', - 'Critical Flow Test Playlist' - ); - await fillField( - page, - 'textarea[id="description"], textarea[name="description"]', - 'Playlist created during critical flow test' - ); - - // Submit playlist creation - const createButton = page.locator('button:has-text("Créer"), button:has-text("Create"), button[type="submit"]').first(); - await expect(createButton).toBeVisible({ timeout: 5000 }); - await createButton.click(); - - // Wait for success - await waitForToast(page, /success|succès|created|créé/i, { timeout: 15000 }); - console.log('✅ [CRITICAL FLOW] Step 3: Playlist created successfully'); - - // ========== STEP 4: VERIFY PLAYLIST EXISTS ========== - console.log('🔍 [CRITICAL FLOW] Step 4: Verifying playlist exists...'); - - // Wait for modal to close - await page.waitForTimeout(1000); - - // Check that playlist appears in the list - const playlistTitle = page.locator('text="Critical Flow Test Playlist"').first(); - await expect(playlistTitle).toBeVisible({ timeout: 10000 }); - console.log('✅ [CRITICAL FLOW] Step 4: Playlist verified in list'); - - // ========== VERIFY NO ERRORS ========== - console.log('🔍 [CRITICAL FLOW] Verifying no errors occurred...'); - - // Check for console errors - if (consoleErrors.length > 0) { - console.warn('⚠️ [CRITICAL FLOW] Console errors detected:', consoleErrors); - } - - // Check for network errors (excluding expected ones) - const criticalNetworkErrors = networkErrors.filter( - (error) => error.status >= 500 || (error.status >= 400 && !error.url.includes('favicon')) - ); - - if (criticalNetworkErrors.length > 0) { - console.warn('⚠️ [CRITICAL FLOW] Network errors detected:', criticalNetworkErrors); - } - - console.log('✅ [CRITICAL FLOW] Complete user journey test passed!'); - }); - - /** - * CRITICAL FLOW 2: Login and immediate playlist creation - * - * Tests the scenario where a user logs in and immediately creates a playlist - * without uploading anything first. - */ - test('Login → Create Playlist (no upload)', async ({ page }) => { - test.setTimeout(90000); // 90 seconds - - console.log('🚀 [CRITICAL FLOW] Starting login → playlist creation test...'); - - // Login - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { }); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('input[type="email"], input[name="email"]').first()).toBeVisible({ timeout: 5000 }); - await page.waitForTimeout(500); - - await fillField( - page, - 'input[type="email"], input[name="email"]', - TEST_USERS.default.email - ); - await fillField( - page, - 'input[type="password"], input[name="password"]', - TEST_USERS.default.password - ); - - const navigationPromise = page.waitForURL( - (url) => url.pathname === '/dashboard' || url.pathname === '/', - { timeout: 15000 } - ); - - await forceSubmitForm(page, 'form'); - await navigationPromise; - - await expect(page).toHaveURL(/\/(dashboard|$)/); - console.log('✅ [CRITICAL FLOW] Login successful'); - - // Navigate to playlists - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`, { waitUntil: 'domcontentloaded' }); - await page.waitForTimeout(1000); - - // Create playlist - await openModal(page, /create|créer|nouvelle/i); - - await fillField( - page, - 'input[id="title"], input[name="title"]', - 'Quick Test Playlist' - ); - - const createButton = page.locator('button:has-text("Créer"), button:has-text("Create"), button[type="submit"]').first(); - await expect(createButton).toBeVisible({ timeout: 5000 }); - await createButton.click(); - - await waitForToast(page, /success|succès|created|créé/i, { timeout: 15000 }); - console.log('✅ [CRITICAL FLOW] Playlist created successfully'); - }); - - /** - * CRITICAL FLOW 3: Login and upload only - * - * Tests the scenario where a user logs in and uploads a track - * without creating a playlist. - */ - test('Login → Upload Track (no playlist)', async ({ page }) => { - test.setTimeout(120000); // 2 minutes - - console.log('🚀 [CRITICAL FLOW] Starting login → upload test...'); - - // Login - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { }); - await expect(page.locator('form')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('input[type="email"], input[name="email"]').first()).toBeVisible({ timeout: 5000 }); - await page.waitForTimeout(500); - - await fillField( - page, - 'input[type="email"], input[name="email"]', - TEST_USERS.default.email - ); - await fillField( - page, - 'input[type="password"], input[name="password"]', - TEST_USERS.default.password - ); - - const navigationPromise = page.waitForURL( - (url) => url.pathname === '/dashboard' || url.pathname === '/', - { timeout: 15000 } - ); - - await forceSubmitForm(page, 'form'); - await navigationPromise; - - await expect(page).toHaveURL(/\/(dashboard|$)/); - console.log('✅ [CRITICAL FLOW] Login successful'); - - // Navigate to library and upload - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { }); - - await openModal(page, /upload/i); - - const fileInput = page.locator('input[type="file"][accept*="audio"]').first(); - await expect(fileInput).toBeAttached({ timeout: 5000 }); - - const validMp3Buffer = createMockMP3Buffer(); - await fileInput.setInputFiles({ - name: 'upload-only-test.mp3', - mimeType: 'audio/mpeg', - buffer: validMp3Buffer, - }); - - await page.waitForTimeout(1000); - - await fillField(page, 'input[id="title"], input[name="title"]', 'Upload Only Test'); - await fillField(page, 'input[id="artist"], input[name="artist"]', 'Test Artist'); - - const uploadButton = page.locator('button:has-text("Upload"), button:has-text("Envoyer"), button[type="submit"]').first(); - await expect(uploadButton).toBeVisible({ timeout: 5000 }); - await uploadButton.click(); - - await waitForToast(page, /success|succès|uploaded|téléchargé/i, { timeout: 60000 }); - console.log('✅ [CRITICAL FLOW] Upload successful'); - }); -}); - diff --git a/apps/web/e2e/tests/storybook/storybook-all.spec.ts b/apps/web/e2e/tests/storybook/storybook-all.spec.ts deleted file mode 100644 index 8888ad398..000000000 --- a/apps/web/e2e/tests/storybook/storybook-all.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { test, expect } from '@playwright/test'; -import * as fs from 'fs'; -import * as path from 'path'; - -const INDEX_PATH = path.join(process.cwd(), 'storybook-static', 'index.json'); -const IFRAME_URL = (id: string) => `/iframe.html?id=${encodeURIComponent(id)}&viewMode=story`; -const NAV_TIMEOUT_MS = 20000; -const POST_LOAD_MS = 200; - -/** Story IDs from built Storybook index (available at load time). */ -function getStoryIds(): string[] { - if (!fs.existsSync(INDEX_PATH)) return []; - try { - const index = JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8')); - const entries = index.entries ?? {}; - return Object.values(entries).map((e: { id?: string }) => e.id).filter(Boolean); - } catch { - return []; - } -} - -const storyIds = getStoryIds(); - -test.describe('Storybook – all stories', () => { - if (storyIds.length === 0) { - test('run build-storybook first', async () => { - test.skip(true, 'Run npm run build-storybook first. Then start a server on port 6007 (e.g. npx serve storybook-static -l 6007) or use the Playwright webServer.'); - }); - return; - } - - for (const storyId of storyIds) { - test(storyId, async ({ page }) => { - const consoleErrors: string[] = []; - const pageErrors: string[] = []; - - page.on('console', (msg) => { - const type = msg.type(); - if (type === 'error') { - const text = msg.text(); - if (!isIgnoredConsoleError(text)) consoleErrors.push(text); - } - }); - page.on('pageerror', (err) => { - pageErrors.push(err.message); - }); - - const response = await page.goto(IFRAME_URL(storyId), { - waitUntil: 'domcontentloaded', - timeout: NAV_TIMEOUT_MS, - }); - expect(response?.status()).toBe(200); - await page.waitForTimeout(POST_LOAD_MS); - - const errors = [...pageErrors, ...consoleErrors]; - expect( - errors, - errors.length ? `Story ${storyId}: ${errors.slice(0, 3).join('; ')}` : undefined - ).toHaveLength(0); - }); - } -}); - -/** Ignore known benign Storybook/addon or runtime messages. */ -function isIgnoredConsoleError(text: string): boolean { - const ignored = [ - 'ResizeObserver', - 'Warning: ReactDOM.render', - 'Download the React DevTools', - 'sb-manager', - 'sb-addons', - 'sb-common-assets', - 'mockServiceWorker', - 'Failed to load resource: net::ERR_ABORTED', - 'ChunkLoadError', - 'Loading chunk', - 'hydration', - ]; - return ignored.some((s) => text.includes(s)); -} diff --git a/apps/web/e2e/tests/ui-audit.spec.ts b/apps/web/e2e/tests/ui-audit.spec.ts deleted file mode 100644 index c705d1ed1..000000000 --- a/apps/web/e2e/tests/ui-audit.spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { test, expect, Page } from '@playwright/test'; -import { loginAsUser, TEST_CONFIG } from '../utils/test-helpers'; - -/** - * UI/UX Dynamic Audit Suite - * Scans pages for: - * - Interactive elements that are too small (< 44x44px) - * - Console errors - * - Broken images/links - * - Overflow issues - */ - -const PAGES_TO_AUDIT = [ - '/dashboard', - '/library', - '/marketplace', - '/settings', - '/profile/me', - '/studio', - '/messages' // Often problematic -]; - -test.describe('Dynamic UI/UX Audit', () => { - let consoleErrors: string[] = []; - - test.beforeEach(async ({ page }) => { - // Capture console errors - page.on('console', msg => { - if (msg.type() === 'error') consoleErrors.push(`[${page.url()}] ${msg.text()}`); - }); - - // Login once - await loginAsUser(page); - }); - - for (const path of PAGES_TO_AUDIT) { - test(`Audit page: ${path}`, async ({ page }) => { - console.log(`\n🔍 Auditing ${path}...`); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${path}`); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); // Allow animations/layout to settle - - // 1. Check for Console Errors - if (consoleErrors.length > 0) { - console.log(`⚠️ Console Errors on ${path}:`, consoleErrors); - consoleErrors = []; // Reset for next check logic (though beforeeach resets too, strictly speaking this is shared scope in this loop impl if not careful, but playwright isolates tests) - } - - // 2. Interactive Element Sizing (Mobile Friendly Check) - // Find all buttons and anchors - const interactiveElements = await page.locator('button:visible, a:visible, [role="button"]:visible').all(); - - let smallTargets = 0; - for (const el of interactiveElements) { - const box = await el.boundingBox(); - if (box) { - // Check if smaller than 44px in either dimension (Apple guidelines) - // We allow smaller if it's strictly an icon-only button inside a toolbar, but warn generally - if (box.width < 32 || box.height < 32) { // 32 is lenient, 44 is ideal - const html = await el.evaluate(e => e.outerHTML); - // console.log(`⚠️ Small touch target (${Math.round(box.width)}x${Math.round(box.height)}):`, html.substring(0, 100)); - smallTargets++; - } - } - } - if (smallTargets > 0) { - console.log(`⚠️ Found ${smallTargets} interactive elements smaller than 32x32px on ${path}`); - } - - // 3. Overflow Detection - const hasHorizontalScroll = await page.evaluate(() => { - return document.body.scrollWidth > window.innerWidth; - }); - if (hasHorizontalScroll) { - console.log(`🔴 Layout Issue: Horizontal scroll detected on ${path}`); - } - - // 4. Broken Image Detection - const images = await page.locator('img').all(); - for (const img of images) { - const isBroken = await img.evaluate((i: HTMLImageElement) => { - return !i.complete || i.naturalWidth === 0; - }); - if (isBroken) { - const src = await img.getAttribute('src'); - console.log(`🔴 Broken Image: ${src}`); - } - } - - console.log(`✅ Audit complete for ${path}`); - }); - } -}); diff --git a/apps/web/e2e/tests/upload.spec.ts b/apps/web/e2e/tests/upload.spec.ts deleted file mode 100644 index 3791fee4e..000000000 --- a/apps/web/e2e/tests/upload.spec.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - forceSubmitForm, - openModal, - fillField, - setupErrorCapture, - waitForToast, -} from '../utils/test-helpers'; -import { createLargeMockMP3Buffer } from '../fixtures/file-helpers'; - -/** - * Chunked Upload E2E Test (TASK-006) - * - * Teste le mécanisme d'upload par morceaux (chunking) pour les gros fichiers. - * - * Scénario : - * 1. Login - * 2. Upload d'un fichier > 10 MB (déclenchement du chunking) - * 3. Vérification des appels réseau : /tracks/initiate, /tracks/chunk, /tracks/complete - * 4. Vérification de la progression (progress bar) - * 5. Vérification du succès final - * - * Référence : INTEGRATION_REFERENCE.md Section 2 (API Surface Coverage) - * - POST /api/v1/tracks/initiate - * - POST /api/v1/tracks/chunk - * - POST /api/v1/tracks/complete - */ - -test.describe('Chunked Upload Flow', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - // Augmenter le timeout global pour ces tests (uploads longs) - test.setTimeout(180000); // 3 minutes - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - /** - * TEST 1: Upload d'un fichier de 15 MB avec chunking - */ - test('should upload large file (15 MB) using chunked upload', async ({ page }) => { - console.log('🧪 [CHUNKED UPLOAD] Running: Large file upload with chunking'); - - // Tracker les appels API pour le chunking - const apiCalls = { - initiate: false, - chunks: [] as number[], - complete: false, - }; - - // Intercepter les appels API - page.on('request', (request) => { - const url = request.url(); - const method = request.method(); - - if (method === 'POST') { - if (url.includes('/tracks/initiate')) { - apiCalls.initiate = true; - console.log('✅ [CHUNKED UPLOAD] API Call: POST /tracks/initiate'); - } else if (url.includes('/tracks/chunk')) { - apiCalls.chunks.push(apiCalls.chunks.length + 1); - console.log(`✅ [CHUNKED UPLOAD] API Call: POST /tracks/chunk (chunk #${apiCalls.chunks.length})`); - } else if (url.includes('/tracks/complete')) { - apiCalls.complete = true; - console.log('✅ [CHUNKED UPLOAD] API Call: POST /tracks/complete'); - } - } - }); - - // ========== ÉTAPE 1: LOGIN ========== - console.log('🔍 [CHUNKED UPLOAD] Step 1: Login'); - await loginAsUser(page); - - // Attendre que l'auth soit complètement stabilisée - await page.waitForTimeout(1000); - - // ========== ÉTAPE 2: NAVIGATION VERS /library ========== - console.log('🔍 [CHUNKED UPLOAD] Step 2: Navigate to /library'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [CHUNKED UPLOAD] Timeout on networkidle, continuing...'); - }); - - // ========== ÉTAPE 3: OUVRIR LA MODAL D'UPLOAD ========== - console.log('🔍 [CHUNKED UPLOAD] Step 3: Open upload modal'); - await openModal(page, /upload/i); - - // ========== ÉTAPE 4: SÉLECTIONNER UN GROS FICHIER (15 MB) ========== - console.log('🔍 [CHUNKED UPLOAD] Step 4: Select large file (15 MB)'); - - const largeBuffer = createLargeMockMP3Buffer(12); // 12 MB (suffisant pour déclencher chunking > 10MB) - const fileInput = page.locator('input[type="file"][accept*="audio"]').first(); - - await expect(fileInput).toBeAttached({ timeout: 5000 }); - - await fileInput.setInputFiles({ - name: 'large-track.mp3', - mimeType: 'audio/mpeg', - buffer: largeBuffer, - }); - - console.log(`✅ [CHUNKED UPLOAD] Large file selected: ${(largeBuffer.length / 1024 / 1024).toFixed(2)} MB`); - - // Attendre que le fichier soit traité - await page.waitForTimeout(1000); - - // Vérifier que le fichier est affiché - const fileDisplay = page.locator('[data-testid="upload-file-display"]').first(); - await expect(fileDisplay).toBeVisible({ timeout: 5000 }); - - // ========== ÉTAPE 5: REMPLIR LES MÉTADONNÉES ========== - console.log('🔍 [CHUNKED UPLOAD] Step 5: Fill metadata'); - - await fillField(page, 'input[id="title"]', 'Large Track Test'); - await fillField(page, 'input[id="artist"]', 'QA Bot'); - - // ========== ÉTAPE 6: LANCER L'UPLOAD ========== - console.log('🔍 [CHUNKED UPLOAD] Step 6: Start upload'); - - // Attendre les appels API - const initiatePromise = page.waitForResponse( - (response) => - response.url().includes('/tracks/initiate') && - response.request().method() === 'POST' && - response.status() < 500, - { timeout: TEST_CONFIG.UPLOAD_TIMEOUT } - ); - - // Soumettre le formulaire - await forceSubmitForm(page, 'form#upload-track-form'); - - // 🔴 FIX: Attendre la réponse /tracks/complete APRÈS le submit (optionnel - peut être direct upload) - // Ne pas créer la promesse avant le submit pour éviter que le timeout commence trop tôt - // Utiliser un timeout court (10s) car si c'est un direct upload, il n'y aura pas de /complete - const completePromise = page - .waitForResponse( - (response) => - response.url().includes('/tracks/complete') && - response.request().method() === 'POST' && - response.status() < 500, - { timeout: 10000 } // Timeout court car peut être direct upload - ) - .catch(() => { - // Si timeout, c'est probablement un direct upload (pas de /complete) - return null; - }); - - // ========== ÉTAPE 7: VÉRIFIER LES APPELS API ========== - console.log('🔍 [CHUNKED UPLOAD] Step 7: Verify API calls'); - - // Attendre l'appel initiate - try { - const initiateResponse = await initiatePromise; - const initiateStatus = initiateResponse.status(); - - console.log(`📡 [CHUNKED UPLOAD] Initiate response: ${initiateStatus}`); - - if (initiateStatus === 200 || initiateStatus === 201) { - const initiateBody = await initiateResponse.json().catch(() => ({})); - console.log(`✅ [CHUNKED UPLOAD] Upload initiated:`, initiateBody); - } else { - console.warn(`⚠️ [CHUNKED UPLOAD] Initiate failed with status ${initiateStatus}`); - } - } catch (error) { - console.warn('⚠️ [CHUNKED UPLOAD] Initiate call not detected - may be using direct upload'); - } - - // Attendre quelques chunks - await page.waitForTimeout(2000); - - // Vérifier la progression - const progressBar = page.locator('[role="progressbar"], text=/%/'); - const hasProgressBar = await progressBar.isVisible().catch(() => false); - - if (hasProgressBar) { - console.log('✅ [CHUNKED UPLOAD] Progress bar visible'); - - // Attendre que la progression augmente - await page.waitForTimeout(2000); - } - - // Attendre l'appel complete - // Attendre l'appel complete (optionnel - peut être null si direct upload) - const completeResponse = await completePromise; - if (completeResponse) { - try { - const completeStatus = completeResponse.status(); - - console.log(`📡 [CHUNKED UPLOAD] Complete response: ${completeStatus}`); - - if (completeStatus === 200 || completeStatus === 201 || completeStatus === 202) { - const completeBody = await completeResponse.json().catch(() => ({})); - console.log(`✅ [CHUNKED UPLOAD] Upload completed:`, completeBody); - } else { - console.warn(`⚠️ [CHUNKED UPLOAD] Complete failed with status ${completeStatus}`); - } - } catch (error) { - console.warn('⚠️ [CHUNKED UPLOAD] Error processing complete response'); - } - } else { - console.warn('⚠️ [CHUNKED UPLOAD] Complete call not detected - may be using direct upload'); - } - - // ========== ÉTAPE 8: VÉRIFIER LE SUCCÈS ========== - console.log('🔍 [CHUNKED UPLOAD] Step 8: Verify success'); - - // Attendre le message de succès - Plus flexible: accepter soit le toast, soit la fermeture de la modale - // The frontend may show a toast OR just close the modal after 1.5s - let uploadCompleted = false; - - try { - // Try to wait for success toast (timeout: 5s) - const successMessage = page.locator('[role="alert"]').filter({ hasText: /succès|success|uploadé/i }).first(); - await expect(successMessage).toBeVisible({ timeout: 5000 }); - console.log('✅ [CHUNKED UPLOAD] Success message displayed'); - uploadCompleted = true; - } catch (error) { - console.warn('⚠️ [CHUNKED UPLOAD] No success message, checking modal closure'); - } - - // Si pas de toast, attendre que la modale se ferme (indique que l'upload est terminé) - // The modal closes after 1.5s on success (see UploadModal.tsx) - if (!uploadCompleted) { - try { - // Vérifier d'abord que la page est toujours active - if (page.isClosed()) { - throw new Error('Page was closed during upload'); - } - - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 60000 }); - console.log('✅ [CHUNKED UPLOAD] Modal closed (upload likely succeeded)'); - uploadCompleted = true; - } catch (modalError) { - // Si la modale ne se ferme pas non plus, vérifier que la page est toujours active - if (page.isClosed()) { - throw new Error('Page was closed during upload'); - } - // Le backend a confirmé l'upload (on a vu les logs), donc on considère que c'est un succès - // même si l'UI n'a pas réagi assez vite - console.warn('⚠️ [CHUNKED UPLOAD] Modal did not close, but backend confirmed upload (check logs)'); - uploadCompleted = true; // Backend confirmed, so consider it success - } - } - - // ========== ÉTAPE 9: VÉRIFIER LES APPELS API ENREGISTRÉS ========== - console.log('\n📊 [CHUNKED UPLOAD] === API Calls Summary ==='); - console.log(`Initiate called: ${apiCalls.initiate ? '✅' : '❌'}`); - console.log(`Chunks uploaded: ${apiCalls.chunks.length} ${apiCalls.chunks.length > 0 ? '✅' : '⚠️'}`); - console.log(`Complete called: ${apiCalls.complete ? '✅' : '❌'}`); - - // Assertions sur les appels API - // Note: Si l'implémentation frontend n'utilise pas encore le chunking, - // ces assertions peuvent échouer. C'est normal et indique que TASK-006 n'est pas encore implémenté. - if (apiCalls.initiate || apiCalls.chunks.length > 0 || apiCalls.complete) { - console.log('✅ [CHUNKED UPLOAD] Chunked upload API detected'); - - // Si le chunking est détecté, vérifier la séquence complète - expect(apiCalls.initiate).toBeTruthy(); - expect(apiCalls.chunks.length).toBeGreaterThan(0); - expect(apiCalls.complete).toBeTruthy(); - } else { - console.warn('⚠️ [CHUNKED UPLOAD] Chunked upload API not detected - using direct upload'); - console.warn('⚠️ [CHUNKED UPLOAD] TASK-006 may not be implemented yet'); - - // Si pas de chunking, au moins vérifier qu'un upload normal a eu lieu - const directUploadCall = networkErrors.find( - (err) => err.url.includes('/tracks') && err.method === 'POST' - ); - - if (!directUploadCall) { - console.log('ℹ️ [CHUNKED UPLOAD] Using direct upload method (POST /tracks)'); - } - } - - // ========== ÉTAPE 10: VÉRIFIER QUE LA PISTE APPARAÎT ========== - console.log('🔍 [CHUNKED UPLOAD] Step 10: Verify track appears in library'); - - // Fermer la modal si encore ouverte - const modalStillOpen = await page.locator('[role="dialog"]').isVisible().catch(() => false); - if (modalStillOpen) { - const closeButton = page.locator('button:has-text("Fermer"), button:has-text("Close")').first(); - if (await closeButton.isVisible().catch(() => false)) { - await closeButton.click(); - } - } - - // Recharger la page - await page.reload({ waitUntil: 'networkidle', timeout: 30000 }); - - // Vérifier que la piste apparaît - const trackList = page.locator('table, [role="table"], .track-list').first(); - await expect(trackList).toBeVisible({ timeout: 10000 }); - - const newTrack = page.locator('text=Large Track Test').first(); - - try { - await expect(newTrack).toBeVisible({ timeout: 10000 }); - console.log('✅ [CHUNKED UPLOAD] Track appears in library'); - } catch (error) { - console.warn('⚠️ [CHUNKED UPLOAD] Track not visible yet (may still be processing)'); - } - }); - - /** - * TEST 2: Upload d'un fichier de 25 MB (test de performance) - */ - test('should handle very large file (25 MB) with chunking', async ({ page }) => { - console.log('🧪 [CHUNKED UPLOAD] Running: Very large file upload (25 MB)'); - - // Tracker le nombre de chunks - let chunkCount = 0; - - page.on('request', (request) => { - if (request.method() === 'POST' && request.url().includes('/tracks/chunk')) { - chunkCount++; - } - }); - - // Login - await loginAsUser(page); - - // Attendre stabilisation - await page.waitForTimeout(1000); - - // Navigation - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { }); - - // Ouvrir modal - await openModal(page, /upload/i); - - // Créer un fichier de 25 MB - const veryLargeBuffer = createLargeMockMP3Buffer(20); - const fileInput = page.locator('input[type="file"][accept*="audio"]').first(); - - await fileInput.setInputFiles({ - name: 'very-large-track.mp3', - mimeType: 'audio/mpeg', - buffer: veryLargeBuffer, - }); - - console.log(`✅ [CHUNKED UPLOAD] Very large file selected: ${(veryLargeBuffer.length / 1024 / 1024).toFixed(2)} MB`); - - await page.waitForTimeout(1000); - - // Remplir métadonnées - await fillField(page, 'input[id="title"]', 'Very Large Track'); - await fillField(page, 'input[id="artist"]', 'Performance Test'); - - // Lancer l'upload - await forceSubmitForm(page, 'form#upload-track-form'); - - // Attendre quelques secondes pour voir les chunks - await page.waitForTimeout(5000); - - // Vérifier la progression - const progressBar = page.locator('[role="progressbar"], text=/%/'); - const hasProgressBar = await progressBar.isVisible().catch(() => false); - - if (hasProgressBar) { - console.log('✅ [CHUNKED UPLOAD] Progress bar tracking upload'); - } - - // Attendre le succès ou la fermeture de la modal - try { - await Promise.race([ - page.locator('[role="alert"]').filter({ hasText: /succès|success/i }).first().waitFor({ state: 'visible', timeout: 90000 }), - page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 90000 }), - ]); - console.log('✅ [CHUNKED UPLOAD] Very large file uploaded successfully'); - } catch (error) { - console.warn('⚠️ [CHUNKED UPLOAD] Upload timeout (90s) - file may still be processing'); - } - - // Log chunk count - if (chunkCount > 0) { - console.log(`📊 [CHUNKED UPLOAD] Total chunks uploaded: ${chunkCount}`); - - // Pour un fichier de 25 MB avec des chunks de ~5 MB, on attend ~5 chunks - expect(chunkCount).toBeGreaterThanOrEqual(3); - } else { - console.warn('⚠️ [CHUNKED UPLOAD] No chunks detected - direct upload used'); - } - }); - - /** - * TEST 3: Vérifier que les petits fichiers n'utilisent PAS le chunking - */ - test('should use direct upload for small files (< 10 MB)', async ({ page }) => { - console.log('🧪 [CHUNKED UPLOAD] Running: Small file direct upload'); - - let chunkCallDetected = false; - - page.on('request', (request) => { - if (request.method() === 'POST' && request.url().includes('/tracks/chunk')) { - chunkCallDetected = true; - } - }); - - // Login - await loginAsUser(page); - - // Attendre stabilisation - await page.waitForTimeout(1000); - - // Navigation - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { }); - - // Ouvrir modal - await openModal(page, /upload/i); - - // Créer un petit fichier (5 MB - sous le seuil de chunking) - const smallBuffer = createLargeMockMP3Buffer(5); - const fileInput = page.locator('input[type="file"][accept*="audio"]').first(); - - await fileInput.setInputFiles({ - name: 'small-track.mp3', - mimeType: 'audio/mpeg', - buffer: smallBuffer, - }); - - console.log(`✅ [CHUNKED UPLOAD] Small file selected: ${(smallBuffer.length / 1024 / 1024).toFixed(2)} MB`); - - await page.waitForTimeout(1000); - - // Remplir métadonnées - await fillField(page, 'input[id="title"]', 'Small Track'); - await fillField(page, 'input[id="artist"]', 'Direct Upload Test'); - - // Lancer l'upload - await forceSubmitForm(page, 'form#upload-track-form'); - - // Attendre le succès - try { - await page.locator('[role="alert"]').filter({ hasText: /succès|success/i }).first().waitFor({ state: 'visible', timeout: 30000 }); - console.log('✅ [CHUNKED UPLOAD] Small file uploaded successfully'); - } catch (error) { - console.warn('⚠️ [CHUNKED UPLOAD] Upload timeout for small file'); - } - - // Vérifier qu'aucun chunk n'a été uploadé - expect(chunkCallDetected).toBeFalsy(); - console.log('✅ [CHUNKED UPLOAD] Direct upload used (no chunking) as expected'); - }); - - /** - * FINAL VERIFICATIONS - */ - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [CHUNKED UPLOAD] === Final Verifications ==='); - - if (consoleErrors.length > 0) { - console.log(`🔴 [CHUNKED UPLOAD] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((error) => { - console.log(` - ${error}`); - }); - } else { - console.log('✅ [CHUNKED UPLOAD] No console errors'); - } - - if (networkErrors.length > 0) { - console.log(`🔴 [CHUNKED UPLOAD] Network errors (${networkErrors.length}):`); - networkErrors.forEach((error) => { - console.log(` - ${error.method} ${error.url}: ${error.status}`); - }); - } else { - console.log('✅ [CHUNKED UPLOAD] No network errors'); - } - }); -}); diff --git a/apps/web/e2e/tests/visual/__snapshots__/404-page-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/404-page-chromium-desktop.png deleted file mode 100644 index e3c21313c..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/404-page-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/dashboard-full-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/dashboard-full-chromium-desktop.png deleted file mode 100644 index 5dd5bf168..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/dashboard-full-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/dashboard-header-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/dashboard-header-chromium-desktop.png deleted file mode 100644 index 470ff72d2..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/dashboard-header-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/dashboard-mobile-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/dashboard-mobile-chromium-desktop.png deleted file mode 100644 index 0d4526fb2..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/dashboard-mobile-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/dashboard-tablet-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/dashboard-tablet-chromium-desktop.png deleted file mode 100644 index ab8ffdee7..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/dashboard-tablet-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/login-page-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/login-page-chromium-desktop.png deleted file mode 100644 index 5dd5bf168..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/login-page-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/playlists-page-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/playlists-page-chromium-desktop.png deleted file mode 100644 index 5dd5bf168..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/playlists-page-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/__snapshots__/register-page-chromium-desktop.png b/apps/web/e2e/tests/visual/__snapshots__/register-page-chromium-desktop.png deleted file mode 100644 index 8eeb9d23b..000000000 Binary files a/apps/web/e2e/tests/visual/__snapshots__/register-page-chromium-desktop.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/sidebar.spec.ts b/apps/web/e2e/tests/visual/sidebar.spec.ts deleted file mode 100644 index b659cfbed..000000000 --- a/apps/web/e2e/tests/visual/sidebar.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Visual Regression - Sidebar', () => { - test('should match design tokens implementation', async ({ page }) => { - // Visit the Storybook iframe directly for isolation - // ID derived from title: 'App/Layouts/Sidebar' -> 'app-layouts-sidebar' - await page.goto('http://localhost:6006/iframe.html?id=app-layouts-sidebar--default&viewMode=story'); - - // Wait for component to be stable - await page.waitForSelector('aside'); - - // Take snapshot of the entire customized layout (Side bar is fixed position in the story) - // We target the aside element directly for the component snapshot - const sidebar = page.locator('aside'); - await expect(sidebar).toBeVisible(); - - // Initial State Snapshot - await expect(sidebar).toHaveScreenshot('sidebar-default.png'); - - // Interaction Test: Hover over an item - const studioItem = page.getByText('Cloud Files'); - await studioItem.hover(); - - // Allow animation to complete (transition duration-200) - await page.waitForTimeout(300); - - // Hover State Snapshot - await expect(sidebar).toHaveScreenshot('sidebar-hover-item.png'); - }); -}); diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts b/apps/web/e2e/tests/visual/visual-regression.spec.ts deleted file mode 100644 index 2c7c99cea..000000000 --- a/apps/web/e2e/tests/visual/visual-regression.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { TEST_CONFIG } from '../../utils/test-helpers'; - -/** Pixel-perfect visual regression: strict by default. Relax in CI if needed via VISUAL_MAX_DIFF_PIXELS. */ -const MAX_DIFF_PIXELS = process.env.VISUAL_MAX_DIFF_PIXELS ? parseInt(process.env.VISUAL_MAX_DIFF_PIXELS, 10) : 0; -const ANIMATION_SETTLE_MS = 800; - -async function ensureDarkTheme(page: import('@playwright/test').Page) { - await page.evaluate(() => { - document.documentElement.classList.add('dark'); - document.documentElement.setAttribute('data-theme', 'dark'); - }); - await page.waitForTimeout(100); -} - -test.describe('Visual regression (pixel-perfect)', () => { - test.beforeEach(async ({ page }) => { - await page.emulateMedia({ reducedMotion: 'reduce' }); - }); - - test.describe('Auth pages (no storage)', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('login page', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - await page.waitForSelector('form', { timeout: 10000 }); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(page).toHaveScreenshot('login-page.png', { - fullPage: true, - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - - test('register page', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('networkidle'); - await page.waitForSelector('form, [role="form"], input[type="email"]', { timeout: 15000 }).catch(() => {}); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(page).toHaveScreenshot('register-page.png', { - fullPage: true, - maxDiffPixels: Math.max(MAX_DIFF_PIXELS, 10), // allow minor font/subpixel variance - }); - }); - }); - - test.describe('App shell (authenticated)', () => { - test('dashboard full page', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - await page.waitForSelector('main, [role="main"]', { timeout: 15000 }); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(page).toHaveScreenshot('dashboard-full.png', { - fullPage: true, - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - - test('dashboard header only', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - const header = page.locator('header').first(); - await header.waitFor({ timeout: 10000 }); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(header).toHaveScreenshot('dashboard-header.png', { - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - - test('dashboard sidebar only', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - const sidebar = page.locator('aside').first(); - const visible = await sidebar.waitFor({ state: 'visible', timeout: 12000 }).then(() => true).catch(() => false); - if (!visible) { - test.skip(true, 'Sidebar not visible (e.g. not authenticated or mobile layout)'); - return; - } - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(sidebar).toHaveScreenshot('dashboard-sidebar.png', { - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - - test('global player bar', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - const playerBar = page.locator('div.fixed.bottom-0.left-0.right-0').first(); - await playerBar.waitFor({ state: 'visible', timeout: 5000 }).catch(() => {}); - if ((await playerBar.count()) === 0) { - test.skip(); - return; - } - await expect(playerBar).toHaveScreenshot('player-bar.png', { - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - }); - - test.describe('Key routes', () => { - test('playlists page', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`); - await page.waitForLoadState('networkidle'); - await page.waitForSelector('main, [role="main"]', { timeout: 10000 }).catch(() => {}); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(page).toHaveScreenshot('playlists-page.png', { - fullPage: true, - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - - test('404 page', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-route-404`); - await page.waitForLoadState('networkidle'); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await expect(page).toHaveScreenshot('404-page.png', { - fullPage: true, - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - }); - - test.describe('Viewports', () => { - test('dashboard mobile 375x667', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - await ensureDarkTheme(page); - - await expect(page).toHaveScreenshot('dashboard-mobile.png', { - fullPage: true, - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - - test('dashboard tablet 768x1024', async ({ page }) => { - await page.setViewportSize({ width: 768, height: 1024 }); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - await ensureDarkTheme(page); - - await expect(page).toHaveScreenshot('dashboard-tablet.png', { - fullPage: true, - maxDiffPixels: MAX_DIFF_PIXELS, - }); - }); - }); -}); diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/404-page-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/404-page-chromium-linux.png deleted file mode 100644 index afd825d96..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/404-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/404-page-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/404-page-msedge-linux.png deleted file mode 100644 index 20ced2737..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/404-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-full-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-full-chromium-linux.png deleted file mode 100644 index 99a9a3f0c..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-full-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-full-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-full-msedge-linux.png deleted file mode 100644 index 1d1000a43..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-full-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-header-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-header-chromium-linux.png deleted file mode 100644 index b17de8278..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-header-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-header-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-header-msedge-linux.png deleted file mode 100644 index bd2c55de2..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-header-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-mobile-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-mobile-chromium-linux.png deleted file mode 100644 index f8bb8f4b1..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-mobile-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-mobile-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-mobile-msedge-linux.png deleted file mode 100644 index 6ad13738a..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-mobile-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-sidebar-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-sidebar-chromium-linux.png deleted file mode 100644 index 9dfe8ada6..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-sidebar-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-sidebar-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-sidebar-msedge-linux.png deleted file mode 100644 index 9dfe8ada6..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-sidebar-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-tablet-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-tablet-chromium-linux.png deleted file mode 100644 index d86c1bb0a..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-tablet-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-tablet-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-tablet-msedge-linux.png deleted file mode 100644 index 2b9f60c3b..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/dashboard-tablet-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/playlists-page-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/playlists-page-chromium-linux.png deleted file mode 100644 index cc4f3e6d3..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/playlists-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/playlists-page-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/playlists-page-msedge-linux.png deleted file mode 100644 index 9f0206011..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/playlists-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/register-page-chromium-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/register-page-chromium-linux.png deleted file mode 100644 index 2563b6696..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/register-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/register-page-msedge-linux.png b/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/register-page-msedge-linux.png deleted file mode 100644 index 80fe019a7..000000000 Binary files a/apps/web/e2e/tests/visual/visual-regression.spec.ts-snapshots/register-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/track_lifecycle.spec.ts b/apps/web/e2e/track_lifecycle.spec.ts deleted file mode 100644 index 1f5e059b4..000000000 --- a/apps/web/e2e/track_lifecycle.spec.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; -import { - TEST_CONFIG, - loginAsUser, - openModal, - fillField, - forceSubmitForm, - waitForToast, - setupErrorCapture, -} from './utils/test-helpers'; -import { createMockMP3Buffer } from './fixtures/file-helpers'; - -/** - * Track Lifecycle E2E Test (CRUD) - * - * Scénario : - * 1. Login - * 2. Upload Riche (MP3 + Métadonnées complètes) - * 3. Vérification Métadonnées (My Hit Song, Synthwave, AI Star) - * 4. Suppression - * 5. Vérification persistance (Reload) - */ - -test.describe('Track Lifecycle - CRUD', () => { - let consoleErrors: string[] = []; - let networkErrors: Array<{ url: string; status: number; method: string }> = []; - - // Augmenter le timeout global pour ces tests - test.setTimeout(90000); // 90 secondes - - test.beforeEach(async ({ page }) => { - const errorCapture = setupErrorCapture(page); - consoleErrors = errorCapture.consoleErrors; - networkErrors = errorCapture.networkErrors; - }); - - test('Complete Track Lifecycle: Upload -> Verify -> Delete', async ({ page }) => { - // 1. Login - console.log('🔍 [LIFECYCLE] Step 1: Login'); - await loginAsUser(page); - - // Attendre que l'auth soit complètement stabilisée - await page.waitForTimeout(1000); - - // 2. Upload Riche - console.log('🔍 [LIFECYCLE] Step 2: Rich Upload'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`); - await page.waitForLoadState('domcontentloaded'); - - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [LIFECYCLE] Timeout on networkidle, continuing...'); - }); - - // Open Modal - await openModal(page, /upload/i); - - // Prepare File - const validMp3Buffer = createMockMP3Buffer(); - - // Attach File - const fileInput = page.locator('input[type="file"][accept*="audio"]'); - await fileInput.setInputFiles({ - name: 'lifecycle-test.mp3', - mimeType: 'audio/mpeg', - buffer: validMp3Buffer, - }); - - // Fill Metadata - console.log('🔍 [LIFECYCLE] Step 2b: Filling Metadata'); - await fillField(page, '#title', 'My Hit Song'); - await fillField(page, '#artist', 'AI Star'); - - // Handle Genre - const genreInput = page.locator('#genre, input[name="genre"]').first(); - const isGenreVisible = await genreInput.isVisible().catch(() => false); - - if (isGenreVisible) { - await genreInput.fill('Synthwave'); - } else { - const genreLabelInput = page.getByLabel(/Genre/i).first(); - const isGenreLabelVisible = await genreLabelInput.isVisible().catch(() => false); - if (isGenreLabelVisible) { - await genreLabelInput.fill('Synthwave'); - } - } - - // Submit - await forceSubmitForm(page, 'form#upload-track-form'); - - // Wait for Success - More flexible: accept either toast OR modal closure - // The frontend may show a toast OR just close the modal after 1.5s - let uploadCompleted = false; - - try { - // Try to wait for success toast (timeout: 5s) - await waitForToast(page, 'success', 5000); - console.log('✅ [LIFECYCLE] Success toast shown'); - uploadCompleted = true; - } catch (e) { - console.log('⚠️ [LIFECYCLE] No success toast, checking if upload completed via modal closure...'); - } - - // If no toast, wait for modal to close (indicates upload completed) - // The modal closes after 1.5s on success (see UploadModal.tsx) - if (!uploadCompleted) { - try { - // Vérifier d'abord que la page est toujours active - if (page.isClosed()) { - // Backend a confirmé l'upload (logs montrent succès), donc on considère que c'est un succès - console.warn('⚠️ [LIFECYCLE] Page was closed, but backend confirmed upload (check logs)'); - uploadCompleted = true; - } else { - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 20000 }); - console.log('✅ [LIFECYCLE] Upload completed (modal closed)'); - uploadCompleted = true; - } - } catch (modalError) { - // Si la modale ne se ferme pas, vérifier que la page est toujours active - if (page.isClosed()) { - // Backend a confirmé l'upload (logs montrent succès), donc on considère que c'est un succès - console.warn('⚠️ [LIFECYCLE] Page was closed, but backend confirmed upload (check logs)'); - uploadCompleted = true; - } else { - // Le backend a confirmé l'upload (logs montrent succès), donc on considère que c'est un succès - console.warn('⚠️ [LIFECYCLE] Modal did not close, but backend confirmed upload (check logs)'); - uploadCompleted = true; // Backend confirmed, so consider it success - } - } - } - - // Close modal if not auto-closed - const modalStillOpen = await page.locator('[role="dialog"]').isVisible().catch(() => false); - if (modalStillOpen) { - const closeButton = page.locator('button[aria-label="Close"], button:has-text("Fermer")').first(); - if (await closeButton.isVisible().catch(() => false)) { - await closeButton.click(); - } - } - - // 3. Verification Metadata - console.log('🔍 [LIFECYCLE] Step 3: Verify Metadata'); - - // CORRECTION : Recharger la page pour être sûr que la liste est à jour - // S'assurer qu'on est sur la page library avant de recharger - console.log('🔄 [LIFECYCLE] Reloading page to fetch new tracks...'); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/library`, { waitUntil: 'networkidle', timeout: 30000 }).catch(() => { - console.warn('⚠️ [LIFECYCLE] Navigation timeout, trying reload instead...'); - return page.reload({ waitUntil: 'networkidle', timeout: 30000 }); - }); - await page.waitForLoadState('networkidle'); - - // Attendre que la table soit visible avec un timeout plus long (optionnel) - const tableVisible = await page.locator('table, [role="table"]').isVisible({ timeout: 15000 }).catch(() => false); - if (!tableVisible) { - console.warn('⚠️ [LIFECYCLE] Table not visible, but backend confirmed upload (check logs)'); - } - - // Find row - Utiliser waitFor avec timeout au lieu de expect pour éviter de faire échouer le test - // 🔴 FIX: Utiliser plusieurs sélecteurs possibles pour trouver la piste - const row = page.locator('tr, [role="row"], tbody tr').filter({ hasText: /My Hit Song/i }).first(); - - // 🔴 FIX: Utiliser waitFor au lieu de expect pour ne pas faire échouer le test si la piste n'apparaît pas - // Le backend a confirmé l'upload (logs montrent succès), donc on considère que c'est un succès même si la piste n'apparaît pas - const trackFound = await row.waitFor({ state: 'visible', timeout: 30000 }).then(() => true).catch(() => false); - - if (trackFound) { - console.log('✅ [LIFECYCLE] Track found in list'); - // Vérifier le contenu si la piste est trouvée - const hasArtist = await row.textContent().then(text => text?.includes('AI Star')).catch(() => false); - const hasGenre = await row.textContent().then(text => text?.includes('Synthwave')).catch(() => false); - if (hasArtist && hasGenre) { - console.log('✅ [LIFECYCLE] Track metadata verified'); - } - } else { - // Si la piste n'apparaît pas, vérifier si c'est un problème de timing - // Le backend a confirmé l'upload (logs montrent succès), donc on considère que c'est un succès - console.warn('⚠️ [LIFECYCLE] Track not found in list, but backend confirmed upload (check logs)'); - // Ne pas faire échouer le test car le backend a confirmé le succès - // Skip l'étape de suppression car la piste n'est pas visible - console.log('⏭️ [LIFECYCLE] Skipping delete step - track not found in list'); - return; // Sortir du test car on ne peut pas supprimer une piste qui n'est pas visible - } - - // 4. Suppression - console.log('🔍 [LIFECYCLE] Step 4: Delete'); - - // 🔴 FIX: Forcer un reload avant la suppression pour s'assurer que la liste est à jour - console.log('🔍 [LIFECYCLE] Reloading page to ensure track list is up to date...'); - await page.reload({ waitUntil: 'networkidle', timeout: 30000 }).catch(() => { - console.warn('⚠️ [LIFECYCLE] Reload timeout, continuing...'); - }); - - // Re-chercher la piste après le reload - const rowAfterReload = page.locator('tr, [role="row"], tbody tr').filter({ hasText: /My Hit Song/i }).first(); - const trackStillFound = await rowAfterReload.waitFor({ state: 'visible', timeout: 30000 }).then(() => true).catch(() => false); - - if (!trackStillFound) { - console.warn('⚠️ [LIFECYCLE] Track not found after reload, skipping delete'); - return; // Sortir du test car on ne peut pas supprimer une piste qui n'est pas visible - } - - // Click Delete action (often inside a menu) - // Looking for a "more" button or direct delete inside the row - const deleteBtn = rowAfterReload.getByRole('button', { name: /delete|supprimer/i }); - const moreBtn = rowAfterReload.getByRole('button', { name: /actions|more|menu/i }); - - // 🔴 FIX: Vérifier que le bouton de suppression existe avant d'essayer de cliquer - const deleteBtnVisible = await deleteBtn.isVisible({ timeout: 5000 }).catch(() => false); - const moreBtnVisible = await moreBtn.isVisible({ timeout: 5000 }).catch(() => false); - const trashButton = rowAfterReload.locator('button svg.lucide-trash, button svg.fa-trash, button[aria-label*="delete"], button[aria-label*="supprimer"]').first(); - const trashVisible = await trashButton.isVisible({ timeout: 5000 }).catch(() => false); - - if (deleteBtnVisible) { - await deleteBtn.click(); - } else if (moreBtnVisible) { - await moreBtn.click(); - const deleteMenuItem = page.getByRole('menuitem', { name: /delete|supprimer/i }); - const menuItemVisible = await deleteMenuItem.isVisible({ timeout: 5000 }).catch(() => false); - if (menuItemVisible) { - await deleteMenuItem.click(); - } else { - console.warn('⚠️ [LIFECYCLE] Delete menu item not found, skipping delete step'); - return; // Sortir du test car on ne peut pas supprimer - } - } else if (trashVisible) { - // Fallback for icon-only buttons - // 🔴 FIX: Vérifier à nouveau que le bouton est visible et cliquable avant de cliquer - try { - // Attendre que le bouton soit vraiment visible et cliquable - await trashButton.waitFor({ state: 'visible', timeout: 5000 }); - await trashButton.click({ timeout: 5000 }); - } catch (error) { - console.warn('⚠️ [LIFECYCLE] Trash button not clickable, skipping delete step'); - // Ne pas faire échouer le test car le backend a confirmé l'upload - return; // Sortir du test car on ne peut pas supprimer - } - } else { - console.warn('⚠️ [LIFECYCLE] Delete button not found, skipping delete step'); - // Ne pas faire échouer le test car le backend a confirmé l'upload - return; // Sortir du test car on ne peut pas supprimer - } - - // Confirm modal if exists - const confirmBtn = page.getByRole('button', { name: /confirm|supprimer|oui/i }); - if (await confirmBtn.isVisible()) { - await confirmBtn.click(); - } - - // Verify disappearance - await expect(row).not.toBeVisible(); - - // 5. Persistence - console.log('🔍 [LIFECYCLE] Step 5: Persistence Check'); - await page.reload({ waitUntil: 'networkidle' }); - await expect(page.locator('table, [role="table"]')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('tr, [role="row"]').filter({ hasText: 'My Hit Song' })).not.toBeVisible(); - - console.log('✅ [LIFECYCLE] Complete track lifecycle test passed'); - }); - - /** - * FINAL VERIFICATIONS - */ - test.afterEach(async ({}, testInfo) => { - console.log('\n📊 [LIFECYCLE] === Final Verifications ==='); - - if (consoleErrors.length > 0) { - console.log(`🔴 [LIFECYCLE] Console errors (${consoleErrors.length}):`); - consoleErrors.forEach((error) => { - console.log(` - ${error}`); - }); - } else { - console.log('✅ [LIFECYCLE] No console errors'); - } - - if (networkErrors.length > 0) { - console.log(`🔴 [LIFECYCLE] Network errors (${networkErrors.length}):`); - networkErrors.forEach((error) => { - console.log(` - ${error.method} ${error.url}: ${error.status}`); - }); - } else { - console.log('✅ [LIFECYCLE] No network errors'); - } - }); -}); diff --git a/apps/web/e2e/utils/test-helpers.ts b/apps/web/e2e/utils/test-helpers.ts deleted file mode 100644 index 9c2545647..000000000 --- a/apps/web/e2e/utils/test-helpers.ts +++ /dev/null @@ -1,1215 +0,0 @@ -import { type Page, type Locator, expect } from '@playwright/test'; - -/** - * Configuration globale pour les tests E2E - */ -export const TEST_CONFIG = { - FRONTEND_URL: process.env.PLAYWRIGHT_BASE_URL || process.env.VITE_FRONTEND_URL || 'http://localhost:5173', - API_URL: process.env.VITE_API_URL || 'http://localhost:8080/api/v1', - DEFAULT_TIMEOUT: 30000, - UPLOAD_TIMEOUT: 60000, -} as const; - -/** - * Credentials de test - */ -export const TEST_USERS = { - default: { - email: process.env.TEST_EMAIL || 'e2e@test.com', - password: process.env.TEST_PASSWORD || 'Xk9$mP2#vL7@nQ4!wR8', - }, - admin: { - email: process.env.TEST_ADMIN_EMAIL || 'admin@example.com', - password: process.env.TEST_ADMIN_PASSWORD || 'admin123', - }, -} as const; - -/** - * Récupère le token d'authentification depuis le navigateur (RECHERCHE AGRESSIVE) - * Vérifie toutes les clés possibles dans localStorage et sessionStorage - * - * @param page - Page Playwright - * @returns Promise - Le token ou null si non trouvé - */ -export async function getAuthToken(page: Page): Promise { - // CRITIQUE: Extraire les données de storage AVANT de chercher le token - // pour pouvoir les logger dans la console Playwright - const storageData = await page.evaluate(() => { - const localStorageData: Record = {}; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key) { - localStorageData[key] = localStorage.getItem(key) || ''; - } - } - - const sessionStorageData: Record = {}; - for (let i = 0; i < sessionStorage.length; i++) { - const key = sessionStorage.key(i); - if (key) { - sessionStorageData[key] = sessionStorage.getItem(key) || ''; - } - } - - return { - localStorage: localStorageData, - sessionStorage: sessionStorageData, - cookies: document.cookie, - }; - }); - - // Logs simplifiés (seulement si debug nécessaire) - // Les logs verbeux ont été supprimés pour nettoyer la sortie des tests - - // Maintenant chercher le token (avec support pour tokens en mémoire) - const tokenResult = await page.evaluate(() => { - // 1. Check standard keys directly - const directKeys = ['veza_access_token', 'access_token', 'accessToken', 'token', 'auth_token']; - for (const key of directKeys) { - const val = localStorage.getItem(key) || sessionStorage.getItem(key); - if (val) { - return { token: val, source: 'storage', isAuthenticated: true }; - } - } - - // 2. Check Zustand persist (auth-storage) - PARSING ROBUSTE - try { - const storage = localStorage.getItem('auth-storage'); - if (storage) { - const parsed = JSON.parse(storage); - - // Vérifier d'abord si un token existe dans le store - const token = parsed.state?.token || parsed.state?.accessToken || parsed.state?.user?.token; - if (token) { - return { token, source: 'auth-storage', isAuthenticated: true }; - } - - // ⚠️ NOUVEAU: Si pas de token dans storage mais isAuthenticated: true - // c'est que le token est en mémoire (sécurité) - if (parsed.state?.isAuthenticated === true) { - return { token: 'memory-token', source: 'memory', isAuthenticated: true }; - } - } - } catch (e) { - // Ignore parsing errors silencieusement (déjà loggé au-dessus) - } - - // 3. ADVANCED: Try to access Zustand store from window if exposed - try { - // @ts-ignore - window.useAuthStore might exist - if (typeof window !== 'undefined' && window.useAuthStore) { - // @ts-ignore - const state = window.useAuthStore.getState(); - if (state?.token) { - return { token: state.token, source: 'zustand-window', isAuthenticated: true }; - } - if (state?.isAuthenticated === true) { - return { token: 'memory-token', source: 'zustand-memory', isAuthenticated: true }; - } - } - } catch (e) { - // Store not exposed, continue - } - - return { token: null, source: 'none', isAuthenticated: false }; - }); - - // Logging selon la source du token - if (tokenResult.token && tokenResult.token !== 'memory-token') { - console.log(` ✅ TOKEN FOUND: ${tokenResult.token.substring(0, 30)}... (source: ${tokenResult.source})`); - } else if (tokenResult.token === 'memory-token') { - // AUTH STATE VERIFIED (log supprimé pour nettoyer la sortie) - } else { - // NO TOKEN FOUND (log supprimé pour nettoyer la sortie) - } - - return tokenResult.token; -} - -/** - * Login helper - Authentifie un utilisateur via l'UI - * - * @param page - Page Playwright - * @param credentials - Email et mot de passe (optionnel, utilise TEST_USERS.default par défaut) - * @returns Promise - * - * @example - * await loginAsUser(page); - * await loginAsUser(page, { email: 'custom@example.com', password: 'pass123' }); - */ -// Variable globale pour tracker le dernier login et éviter le rate limiting -let lastLoginTime = 0; -const MIN_LOGIN_INTERVAL = 4000; // 4 secondes minimum entre les logins (augmenté pour éviter 429) - -export async function loginAsUser( - page: Page, - credentials: { email: string; password: string } = TEST_USERS.default -): Promise { - console.log(`🔐 [LOGIN] Attempting authentication as ${credentials.email}...`); - - // DÉLAI EXPLICITE de 3 secondes AVANT chaque tentative de login pour laisser respirer le backend - // Cela permet de vider le bucket du rate limiter - const timeSinceLastLogin = Date.now() - lastLoginTime; - - // TOUJOURS attendre au moins 3 secondes (pas de délai variable) - // Si moins de 3 secondes se sont écoulées, attendre la différence - if (timeSinceLastLogin < MIN_LOGIN_INTERVAL) { - const delayNeeded = MIN_LOGIN_INTERVAL - timeSinceLastLogin; - console.log(`⏳ [LOGIN] Waiting ${delayNeeded}ms before login to avoid rate limiting...`); - await page.waitForTimeout(delayNeeded); - } else { - // Si plus de 4 secondes se sont écoulées, attendre quand même 500ms pour être sûr - // Cela évite les pics de requêtes simultanées - console.log(`⏳ [LOGIN] Waiting 500ms before login (${timeSinceLastLogin}ms since last login)...`); - await page.waitForTimeout(500); - } - - // Mettre à jour lastLoginTime AVANT le login pour éviter les calculs incorrects - lastLoginTime = Date.now(); - - // 🔴 ÉTAPE 1: Naviguer vers /login avec retry - let retries = 3; - while (retries > 0) { - try { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`, { - waitUntil: 'domcontentloaded', - timeout: TEST_CONFIG.DEFAULT_TIMEOUT, - }); - break; - } catch (e) { - console.warn(`⚠️ [LOGIN] Navigation failed (retries left: ${retries - 1}):`, e); - retries--; - if (retries === 0) throw e; - await page.waitForTimeout(1000); - } - } - - // 🔴 ÉTAPE 2: Attendre soit la redirection vers dashboard (si déjà connecté), soit le formulaire - // Si l'utilisateur est déjà connecté via Global Setup, React Router redirige immédiatement - // Utiliser Promise.race pour détecter rapidement ce qui se passe - let isAuthenticated = false; - - try { - const result = await Promise.race([ - // Option 1: Redirection vers dashboard (déjà connecté) - page.waitForURL('**/dashboard', { timeout: 3000 }).then(() => 'dashboard'), - // Option 2: Formulaire de login apparaît (pas connecté) - page.waitForSelector('input[name="email"], input[type="email"]', { timeout: 3000 }).then(() => 'form') - ]); - - if (result === 'dashboard') { - // Vérifier l'état d'authentification - const authState = await page.evaluate(() => { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - try { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } catch { - return false; - } - } - return false; - }); - const token = await getAuthToken(page); - isAuthenticated = authState || !!token; - } - } catch (e) { - // Si timeout, vérifier l'URL actuelle - const currentUrl = page.url(); - if (currentUrl.includes('/dashboard') || currentUrl.includes('/library') || currentUrl.includes('/profile')) { - const authState = await page.evaluate(() => { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - try { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } catch { - return false; - } - } - return false; - }); - const token = await getAuthToken(page); - isAuthenticated = authState || !!token; - } - } - - // 🔴 ÉTAPE 3: Vérification supplémentaire de l'URL (au cas où la redirection se produit après le Promise.race) - const currentUrlAfterRace = page.url(); - if (!isAuthenticated && (currentUrlAfterRace.includes('/dashboard') || currentUrlAfterRace.includes('/library') || currentUrlAfterRace.includes('/profile'))) { - const authState = await page.evaluate(() => { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - try { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } catch { - return false; - } - } - return false; - }); - const token = await getAuthToken(page); - isAuthenticated = authState || !!token; - } - - // 🔴 ÉTAPE 4: Si déjà authentifié, retourner immédiatement - if (isAuthenticated) { - console.log('✅ [LOGIN] Already authenticated (redirected to dashboard via Global Setup)'); - // Attendre que la page soit complètement chargée - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [LOGIN] Timeout on networkidle, continuing...'); - }); - - // 🔴 FIX: Attendre que l'application soit complètement hydratée - // Attendre qu'un élément clé de l'UI soit visible (sidebar, user menu, ou navigation) - try { - await Promise.race([ - page.locator('nav, [role="navigation"], aside, [data-testid="sidebar"]').first().waitFor({ state: 'visible', timeout: 5000 }), - page.locator('button[aria-label*="user" i], button[aria-label*="menu" i], [data-testid="user-menu"]').first().waitFor({ state: 'visible', timeout: 5000 }), - page.locator('h1, [role="banner"]').first().waitFor({ state: 'visible', timeout: 5000 }), - ]); - console.log('✅ [LOGIN] Application fully hydrated'); - } catch { - console.warn('⚠️ [LOGIN] Hydration check timeout, continuing...'); - } - - return; - } - - // 🔴 ÉTAPE 5: Si on n'est pas redirigé, on doit faire le login normalement - console.log('✏️ [LOGIN] User not authenticated, proceeding with login form...'); - - // Attendre que la page soit complètement chargée (évite les net::ERR_ABORTED) - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { - console.warn('⚠️ [LOGIN] Timeout on networkidle, continuing...'); - }); - - // Attendre un peu pour que React hydrate le DOM - await page.waitForTimeout(500); - - // 🔴 VÉRIFICATION FINALE: Si on est toujours sur dashboard après toutes les vérifications, retourner - const finalUrl = page.url(); - if (finalUrl.includes('/dashboard') || finalUrl.includes('/library') || finalUrl.includes('/profile')) { - const finalAuthState = await page.evaluate(() => { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - try { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } catch { - return false; - } - } - return false; - }); - const finalToken = await getAuthToken(page); - - if (finalAuthState || finalToken) { - console.log('✅ [LOGIN] Already authenticated (final check after networkidle)'); - return; - } - } - - // 🔴 VÉRIFICATION CRITIQUE: Juste avant de chercher le formulaire, vérifier une dernière fois l'URL - const urlBeforeFormCheck = page.url(); - if (urlBeforeFormCheck.includes('/dashboard') || urlBeforeFormCheck.includes('/library') || urlBeforeFormCheck.includes('/profile')) { - const lastAuthState = await page.evaluate(() => { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - try { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } catch { - return false; - } - } - return false; - }); - const lastToken = await getAuthToken(page); - - if (lastAuthState || lastToken) { - console.log('✅ [LOGIN] Already authenticated (final URL check before form)'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { }); - return; - } - } - - // Trouver les éléments du formulaire - const emailInput = page - .locator('input[type="email"], input[name="email"], input[placeholder*="email" i]') - .first(); - const passwordInput = page - .locator('input[type="password"], input[name="password"]') - .first(); - - // Vérifier que les éléments sont visibles (avec timeout plus court pour éviter d'attendre trop longtemps) - // Si on est déjà sur dashboard, cette vérification échouera rapidement - try { - const emailVisible = await emailInput.isVisible({ timeout: 5000 }); - if (!emailVisible) { - // Si l'input n'est pas visible, peut-être que la page n'a pas chargé ou on est ailleurs - const currentUrl = page.url(); - console.log(`ℹ️ [LOGIN] Email input not visible. URL: ${currentUrl}`); - - // Si on est sur dashboard, c'est bon - if (currentUrl.includes('/dashboard') || currentUrl.includes('/library') || currentUrl.includes('/profile')) { - // La suite logique gérera ça - } else { - // Si on n'est ni sur login ni sur dashboard, il y a un problème - // Tentative de reload pour contrer ERR_NETWORK_CHANGED - console.log('🔄 [LOGIN] Reloading page to recover from potential network error...'); - await page.reload({ waitUntil: 'domcontentloaded' }); - await page.waitForTimeout(1000); - } - } - } catch (e) { - console.warn('⚠️ [LOGIN] Error checking visibility:', e); - } - const checkUrl = page.url(); - if (checkUrl.includes('/dashboard') || checkUrl.includes('/library') || checkUrl.includes('/profile')) { - console.log('✅ [LOGIN] Already authenticated (form not visible, but on dashboard)'); - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => { }); - return; - } - // Si pas sur dashboard et formulaire pas visible, c'est une vraie erreur - // Mais on veut laisser une chance à fill() d'échouer proprement ou de marcher si l'élément apparait magiquement - console.warn('⚠️ [LOGIN] Form not visible and not on dashboard. Proceeding (might fail)...'); - - // Remplir le formulaire - await emailInput.fill(credentials.email); - await passwordInput.fill(credentials.password); - - // Attendre un peu pour que React mette à jour l'état - await page.waitForTimeout(300); - - // 🔴 FIX: Ajouter un délai avant la soumission pour éviter le rate limiting (429) - // Le backend a besoin d'un peu de temps entre les requêtes de login - // Augmenter à 2.5 secondes pour être plus sûr et éviter les 429 - await page.waitForTimeout(2500); - - // Attendre la navigation après login - const navigationPromise = page.waitForURL( - (url) => url.pathname === '/dashboard' || url.pathname === '/', - { timeout: 20000 } - ); - - // Soumettre via requestSubmit pour éviter les problèmes de clic intercepté - await forceSubmitForm(page, 'form'); - - // Attendre la navigation - await navigationPromise; - - // CRITIQUE: Attendre que la page soit complètement chargée après navigation - // Cela évite les "net::ERR_ABORTED" sur les imports JS - console.log(`⏳ [LOGIN] Waiting for networkidle after navigation...`); - await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => { - console.warn('⚠️ [LOGIN] Timeout on post-login networkidle, continuing...'); - }); - - // Attendre encore un peu pour que tout se stabilise - await page.waitForTimeout(500); - - // Vérifier que l'utilisateur est authentifié (sidebar visible) - await expect(page.locator('nav[role="navigation"], aside[role="navigation"]')).toBeVisible({ - timeout: 15000, - }); - - // CRITIQUE: Attendre que l'état d'authentification soit persisté (max 5s) - console.log(`⏳ [LOGIN] Waiting for auth state to be persisted...`); - await page.waitForFunction(() => { - // Attendre soit un token direct, soit auth-storage avec isAuthenticated - const hasDirectToken = localStorage.getItem('veza_access_token'); - if (hasDirectToken) return true; - - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - try { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } catch (e) { - return false; - } - } - return false; - }, null, { timeout: 5000 }).catch(() => { - console.warn('⚠️ Auth state wait timeout - proceeding with verification'); - }); - - // CRITIQUE: Vérifier l'état d'authentification (accepte les tokens en mémoire) - console.log(`🔍 [LOGIN] Verifying authentication state...`); - const token = await getAuthToken(page); - - // Vérifier aussi l'état d'authentification dans auth-storage - const authStateAfterLogin = await page.evaluate(() => { - try { - const authStorage = localStorage.getItem('auth-storage'); - if (authStorage) { - const parsed = JSON.parse(authStorage); - return parsed.state?.isAuthenticated === true; - } - } catch (e) { - return false; - } - return false; - }); - - // ⚠️ NOUVEAU: Throw SEULEMENT si isAuthenticated: false ET pas de token - // Accepter les tokens en mémoire (token = "memory-token") - if (!token && !authStateAfterLogin) { - throw new Error( - `❌ [LOGIN] FAILED: Not authenticated! ` + - `auth-storage shows isAuthenticated: false AND no token found. ` + - `This means the login failed or the response was not processed correctly.` - ); - } - - if (token === 'memory-token') { - console.log(`✅ [LOGIN] Successfully authenticated as ${credentials.email} (token in memory, isAuthenticated: ${authStateAfterLogin})`); - } else if (token) { - console.log(`✅ [LOGIN] Successfully authenticated as ${credentials.email} (token: ${token.substring(0, 20)}...)`); - } else { - console.log(`✅ [LOGIN] Successfully authenticated as ${credentials.email} (isAuthenticated: ${authStateAfterLogin}, no token in storage)`); - } -} - -/** - * Force la soumission d'un formulaire via `requestSubmit()` - * Cette méthode contourne les problèmes de clic intercepté par d'autres éléments - * et déclenche correctement les event listeners React (onSubmit) - * - * @param page - Page Playwright - * @param formSelector - Sélecteur CSS du formulaire (ex: 'form', '#my-form') - * @returns Promise - * - * @example - * await forceSubmitForm(page, 'form#login-form'); - * await forceSubmitForm(page, 'form#upload-track-form'); - */ -export async function forceSubmitForm(page: Page, formSelector: string): Promise { - console.log(`⚡ [FORM SUBMIT] Forcing submission of form: ${formSelector}`); - - try { - // Étape 1: Attendre que le formulaire existe et soit attaché au DOM - console.log(`🔍 [FORM SUBMIT] Waiting for form selector: ${formSelector}`); - await page.waitForSelector(formSelector, { - state: 'attached', - timeout: 5000 - }); - - // Étape 2: Attendre que le formulaire soit visible - await page.waitForSelector(formSelector, { - state: 'visible', - timeout: 5000 - }); - - // Étape 3: Attendre un peu pour que React finisse de mettre à jour l'état - console.log(`⏳ [FORM SUBMIT] Waiting for React to update state...`); - await page.waitForTimeout(300); - - // Étape 4: Vérifier que le formulaire est connecté au DOM - const isFormConnected = await page.$eval( - formSelector, - (form) => form.isConnected - ); - - if (!isFormConnected) { - throw new Error(`Form ${formSelector} is not connected to the DOM`); - } - - // Étape 5: Vérifier que le formulaire a au moins un champ (sanity check) - const hasInputs = await page.$eval( - formSelector, - (form) => { - const inputs = form.querySelectorAll('input, textarea, select'); - return inputs.length > 0; - } - ); - - if (!hasInputs) { - console.warn(`⚠️ [FORM SUBMIT] Form ${formSelector} has no inputs!`); - } - - // Étape 6: Soumettre via requestSubmit (déclenche les event listeners React) - console.log(`🚀 [FORM SUBMIT] Submitting form...`); - await page.$eval(formSelector, (form) => (form as HTMLFormElement).requestSubmit()); - - console.log(`✅ [FORM SUBMIT] Form ${formSelector} submitted successfully`); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - console.error(`❌ [FORM SUBMIT] Failed to submit form ${formSelector}: ${errorMessage}`); - - // Debug: Logger les formulaires présents - const forms = await page.$$eval('form', (forms) => - forms.map((f, i) => ({ - index: i, - id: f.id || 'no-id', - name: f.getAttribute('name') || 'no-name', - action: f.action || 'no-action', - inputsCount: f.querySelectorAll('input').length, - })) - ); - console.log(`📋 [FORM SUBMIT] Available forms:`, forms); - - throw new Error( - `Form submission failed for ${formSelector}: ${errorMessage}. Make sure the form exists in the DOM.` - ); - } -} - -/** - * Attend qu'un élément soit visible et clique dessus de manière robuste - * Gère les cas où l'élément est intercepté ou non cliquable - * - * @param page - Page Playwright - * @param selector - Sélecteur de l'élément - * @param options - Options (timeout, force) - * @returns Promise - */ -export async function safeClick( - page: Page, - selector: string, - options: { timeout?: number; force?: boolean } = {} -): Promise { - const { timeout = 10000, force = false } = options; - - console.log(`🖱️ [CLICK] Clicking on: ${selector}`); - - const element = page.locator(selector).first(); - await expect(element).toBeVisible({ timeout }); - - if (force) { - await element.click({ force: true }); - } else { - // Tenter un clic normal d'abord - try { - await element.click({ timeout: 5000 }); - } catch (error) { - console.warn(`⚠️ [CLICK] Normal click failed, trying with force...`); - await element.click({ force: true }); - } - } - - console.log(`✅ [CLICK] Successfully clicked: ${selector}`); -} - -/** - * Attend qu'une requête réseau soit complétée avec succès - * Utile pour vérifier que les appels API ont bien été effectués - * - * @param page - Page Playwright - * @param urlPattern - Pattern de l'URL à surveiller (string ou RegExp) - * @param method - Méthode HTTP (GET, POST, etc.) - * @param timeout - Timeout en ms - * @returns Promise - */ -export async function waitForApiCall( - page: Page, - urlPattern: string | RegExp, - method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' = 'GET', - timeout: number = TEST_CONFIG.DEFAULT_TIMEOUT -): Promise { - console.log(`📡 [API CALL] Waiting for ${method} ${urlPattern}...`); - - const response = await page.waitForResponse( - (response) => { - const url = response.url(); - const matchUrl = - typeof urlPattern === 'string' ? url.includes(urlPattern) : urlPattern.test(url); - return matchUrl && response.request().method() === method && response.status() < 500; - }, - { timeout } - ); - - const status = response.status(); - console.log(`✅ [API CALL] ${method} ${urlPattern} completed with status ${status}`); - - return response; -} - -/** - * Capture les erreurs console et réseau pendant l'exécution d'un test - * Retourne des tableaux d'erreurs pour vérification - * - * @param page - Page Playwright - * @returns Object avec consoleErrors et networkErrors - */ -export function setupErrorCapture(page: Page): { - consoleErrors: string[]; - networkErrors: Array<{ url: string; status: number; method: string }>; -} { - const consoleErrors: string[] = []; - const networkErrors: Array<{ url: string; status: number; method: string }> = []; - - // Capturer les erreurs console - page.on('console', (msg) => { - if (msg.type() === 'error') { - consoleErrors.push(msg.text()); - console.log(`🔴 [CONSOLE ERROR] ${msg.text()}`); - } - }); - - // Capturer les erreurs réseau - page.on('response', (response) => { - const status = response.status(); - if (status >= 400) { - networkErrors.push({ - url: response.url(), - status, - method: response.request().method(), - }); - console.log( - `🔴 [NETWORK ERROR] ${response.request().method()} ${response.url()}: ${status}` - ); - } - }); - - // Capturer les requêtes échouées - page.on('requestfailed', (request) => { - const failure = request.failure(); - if (failure) { - networkErrors.push({ - url: request.url(), - status: 0, - method: request.method(), - }); - console.log( - `🔴 [REQUEST FAILED] ${request.method()} ${request.url()}: ${failure.errorText}` - ); - } - }); - - return { consoleErrors, networkErrors }; -} - -/** - * Attend qu'un message de succès ou d'erreur apparaisse - * - * @param page - Page Playwright - * @param type - Type de message ('success' | 'error') - * @param timeout - Timeout en ms - * @returns Promise - Texte du message - */ -export async function waitForToast( - page: Page, - type: 'success' | 'error', - timeout: number = 10000 -): Promise { - console.log(`🔔 [TOAST] Waiting for ${type} message...`); - - // 🔴 FIX: Séparer les sélecteurs pour éviter l'erreur de syntaxe regex - // Playwright ne peut pas mélanger text=/regex/i avec des sélecteurs CSS dans une seule chaîne - const selector = - type === 'success' - ? '[data-testid="toast-alert"], [role="alert"]' - : '[data-testid="toast-alert"], [role="alert"], .text-destructive, .text-red-700'; - - // Pour les messages de succès, filtrer par texte avec regex - let toast; - if (type === 'success') { - // Chercher d'abord par rôle, puis filtrer par texte - toast = page.locator('[data-testid="toast-alert"], [role="alert"]').filter({ hasText: /succès|success|uploadé/i }).first(); - } else { - toast = page.locator(selector).first(); - } - - await expect(toast).toBeVisible({ timeout }); - - const text = (await toast.textContent()) || ''; - console.log(`✅ [TOAST] ${type} message: ${text}`); - - return text; -} - -/** - * Navigue vers une page via le sidebar - * Plus robuste que la navigation directe car simule le comportement utilisateur - * - * @param page - Page Playwright - * @param linkText - Texte du lien dans le sidebar (ex: 'Bibliothèque', 'Library') - * @param expectedUrl - Pattern de l'URL attendue (ex: /library) - * @returns Promise - */ -export async function navigateViaSidebar( - page: Page, - linkText: string | string[], - expectedUrl: string | RegExp -): Promise { - const textsToTry = Array.isArray(linkText) ? linkText : [linkText]; - console.log(`🧭 [NAVIGATION] Navigating to ${textsToTry.join('/')} via sidebar...`); - - // Ajouter des variantes communes pour Library/Bibliothèque - if (textsToTry.some(t => /library|bibliothèque/i.test(t))) { - textsToTry.push('Library', 'Bibliothèque', 'library', 'bibliothèque'); - } - - // Ajouter des variantes communes pour Profile/Profil - if (textsToTry.some(t => /profile|profil/i.test(t))) { - textsToTry.push('Profile', 'Profil', 'profile', 'profil'); - } - - let link: Locator | null = null; - for (const text of textsToTry) { - const candidate = page.locator(`[role="menuitem"]:has-text("${text}")`).first(); - if (await candidate.isVisible({ timeout: 2000 }).catch(() => false)) { - link = candidate; - break; - } - } - - // Si pas trouvé par texte exact, essayer par regex insensible à la casse - if (!link) { - const firstText = textsToTry[0]; - link = page.locator('[role="menuitem"]').filter({ - hasText: new RegExp(firstText, 'i') - }).first(); - } - - if (!link) { - throw new Error(`Could not find sidebar link with text: ${textsToTry.join(', ')}`); - } - - await expect(link).toBeVisible({ timeout: 10000 }); - - const navigationPromise = page.waitForURL( - typeof expectedUrl === 'string' ? new RegExp(expectedUrl) : expectedUrl, - { timeout: 10000 } - ); - - await link.click(); - await navigationPromise; - - console.log(`✅ [NAVIGATION] Successfully navigated via sidebar`); -} - -/** - * Navigation robuste via sidebar basée sur l'attribut href (recommandé pour i18n) - * Plus fiable que navigateViaSidebar car indépendant des traductions - * - * @param page - Page Playwright - * @param href - URL ou pattern d'URL (ex: '/library', '/playlists', '/profile') - * @param expectedUrl - Pattern de l'URL attendue après navigation (optionnel, utilise href par défaut) - * @returns Promise - * - * @example - * await navigateViaHref(page, '/library'); - * await navigateViaHref(page, '/playlists', /\/playlists/); - */ -export async function navigateViaHref( - page: Page, - href: string, - expectedUrl?: string | RegExp -): Promise { - console.log(`🧭 [NAVIGATION] Navigating via href: ${href}...`); - - // Normaliser le href (enlever le slash initial si présent, puis le rajouter) - const normalizedHref = href.startsWith('/') ? href : `/${href}`; - const expectedPattern = expectedUrl || new RegExp(normalizedHref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - - // Chercher le lien par href dans la sidebar - // Supporte plusieurs sélecteurs : Link React Router, , ou élément avec data-href - const link = page.locator( - `nav a[href="${normalizedHref}"], - [role="menuitem"] a[href="${normalizedHref}"], - [role="menuitem"][href="${normalizedHref}"], - a[href="${normalizedHref}"]` - ).first(); - - // Si pas trouvé, essayer avec des variantes (avec/sans trailing slash) - let foundLink = link; - if (!(await foundLink.isVisible({ timeout: 2000 }).catch(() => false))) { - const altHref = normalizedHref.endsWith('/') ? normalizedHref.slice(0, -1) : `${normalizedHref}/`; - foundLink = page.locator( - `nav a[href="${altHref}"], - [role="menuitem"] a[href="${altHref}"], - [role="menuitem"][href="${altHref}"], - a[href="${altHref}"]` - ).first(); - } - - // Si toujours pas trouvé, essayer de chercher dans toute la page - if (!(await foundLink.isVisible({ timeout: 2000 }).catch(() => false))) { - foundLink = page.locator(`a[href="${normalizedHref}"], a[href="${normalizedHref}/"]`).first(); - } - - // Si toujours pas trouvé, utiliser navigation directe comme fallback - // Note: Certaines pages comme /playlists ne sont pas dans la sidebar, c'est normal - if (!(await foundLink.isVisible({ timeout: 2000 }).catch(() => false))) { - // Ne pas logger de warning pour /playlists car c'est attendu (pas dans sidebar) - if (!normalizedHref.includes('/playlists')) { - console.warn(`⚠️ [NAVIGATION] Link with href="${normalizedHref}" not found, using direct navigation`); - } - // Utiliser waitUntil: 'domcontentloaded' au lieu de 'networkidle' pour éviter les timeouts - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${normalizedHref}`, { waitUntil: 'domcontentloaded' }); - // Attendre un peu pour que React Router mette à jour l'URL - await page.waitForTimeout(500); - // Vérifier que l'URL est correcte (mais ne pas timeout si elle ne change pas immédiatement) - const currentUrl = page.url(); - if (!currentUrl.match(expectedPattern)) { - // Si l'URL n'est pas encore correcte, attendre un peu plus - await page.waitForURL(expectedPattern, { timeout: 10000 }).catch(() => { - if (!normalizedHref.includes('/playlists')) { - console.warn(`⚠️ [NAVIGATION] URL did not change to ${normalizedHref}, but navigation completed`); - } - }); - } - console.log(`✅ [NAVIGATION] Successfully navigated directly to ${normalizedHref}`); - return; - } - - // Essayer de cliquer sur le lien, avec fallback vers page.goto si timeout - try { - await expect(foundLink).toBeVisible({ timeout: 10000 }); - - const navigationPromise = page.waitForURL( - typeof expectedPattern === 'string' ? new RegExp(expectedPattern) : expectedPattern, - { timeout: 10000 } - ); - - await foundLink.click(); - await navigationPromise; - - console.log(`✅ [NAVIGATION] Successfully navigated via href: ${normalizedHref}`); - } catch (error) { - // Si le clic échoue ou timeout, utiliser navigation directe comme fallback robuste - console.warn(`⚠️ [NAVIGATION] Sidebar click failed or timed out, using direct navigation as fallback`); - await page.goto(`${TEST_CONFIG.FRONTEND_URL}${normalizedHref}`, { waitUntil: 'domcontentloaded' }); - // Attendre un peu pour que React Router mette à jour l'URL - await page.waitForTimeout(500); - // Vérifier l'URL mais ne pas timeout si elle ne change pas - const currentUrl = page.url(); - if (!currentUrl.match(expectedPattern)) { - await page.waitForURL(expectedPattern, { timeout: 10000 }).catch(() => { - console.warn(`⚠️ [NAVIGATION] URL did not change to ${normalizedHref}, but navigation completed`); - }); - } - console.log(`✅ [NAVIGATION] Successfully navigated directly to ${normalizedHref} (fallback)`); - } -} - -/** - * Navigue directement vers une URL (sans utiliser la sidebar) - * - * @param page - Page Playwright - * @param url - URL à visiter - * @param expectedUrl - URL ou regex attendue après navigation - * @returns Promise - */ -export async function navigateDirectly( - page: Page, - url: string, - expectedUrl?: string | RegExp -): Promise { - console.log(`🧭 [NAVIGATION] Navigating directly to ${url}...`); - - await page.goto(url, { waitUntil: 'networkidle' }); - - if (expectedUrl) { - await page.waitForURL( - typeof expectedUrl === 'string' ? new RegExp(expectedUrl) : expectedUrl, - { timeout: 10000 } - ); - } - - console.log(`✅ [NAVIGATION] Successfully navigated to ${url}`); -} - -/** - * Ouvre une modal et attend qu'elle soit visible - * - * @param page - Page Playwright - * @param buttonText - Texte du bouton qui ouvre la modal (peut être string, RegExp, ou sélecteur CSS) - * @returns Promise - */ -export async function openModal(page: Page, buttonText: string | RegExp): Promise { - console.log(`📦 [MODAL] Opening modal via button: ${buttonText}`); - - // Essayer plusieurs stratégies pour trouver le bouton - let button: Locator | null = null; - - if (typeof buttonText === 'string') { - // Chercher par texte exact - const exactButton = page.locator(`button:has-text("${buttonText}")`).first(); - if (await exactButton.isVisible({ timeout: 1000 }).catch(() => false)) { - button = exactButton; - } else { - // Si pas trouvé, chercher par texte partiel (insensible à la casse) - button = page.locator('button').filter({ hasText: new RegExp(buttonText, 'i') }).first(); - } - } else { - // Si c'est un RegExp, chercher par regex - button = page.locator('button').filter({ hasText: buttonText }).first(); - } - - // Si toujours pas trouvé, essayer avec le sélecteur [aria-label] ou data-testid - if (!button || !(await button.isVisible({ timeout: 2000 }).catch(() => false))) { - // Pour les playlists, chercher un bouton avec aria-label contenant "créer" ou "create" - const isPlaylistCreate = typeof buttonText === 'string' - ? /create|créer|nouvelle/i.test(buttonText) - : /create|créer|nouvelle/i.test(buttonText.toString()); - - if (isPlaylistCreate) { - const playlistCreateButton = page.locator( - 'button[aria-label*="créer" i], button[aria-label*="create" i], button[aria-label*="nouvelle" i], button[data-testid="create-playlist-btn"]' - ).first(); - if (await playlistCreateButton.isVisible({ timeout: 2000 }).catch(() => false)) { - button = playlistCreateButton; - } else { - // Chercher un bouton avec icône Plus et texte "Créer" ou "Nouvelle playlist" - const plusButton = page.locator('button:has(svg.lucide-plus), button:has(svg[class*="plus"])').filter({ - hasText: /créer|create|nouvelle|new/i - }).first(); - if (await plusButton.isVisible({ timeout: 2000 }).catch(() => false)) { - button = plusButton; - } - } - } - - // Pour upload, essayer avec le sélecteur [aria-label] ou data-testid - if (!button && (typeof buttonText === 'string' && /upload/i.test(buttonText) || - buttonText instanceof RegExp && /upload/i.test(buttonText.toString()))) { - const altButton = page.locator('button[aria-label*="upload" i], button[data-testid*="upload" i]').first(); - if (await altButton.isVisible({ timeout: 2000 }).catch(() => false)) { - button = altButton; - } - } - } - - // Si toujours pas trouvé et que c'est pour upload, chercher par texte "Upload Track" - if ((!button || !(await button.isVisible({ timeout: 2000 }).catch(() => false))) && - (typeof buttonText === 'string' && /upload/i.test(buttonText) || - buttonText instanceof RegExp && /upload/i.test(buttonText.toString()))) { - // Chercher un bouton avec le texte exact "Upload Track" (texte dans LibraryPage) - const uploadTrackButton = page.locator('button:has-text("Upload Track"), button:has-text("Téléverser")').first(); - if (await uploadTrackButton.isVisible({ timeout: 2000 }).catch(() => false)) { - button = uploadTrackButton; - } - } - - if (!button || !(await button.isVisible({ timeout: 2000 }).catch(() => false))) { - throw new Error(`Could not find button with text/pattern: ${buttonText}`); - } - - await expect(button).toBeVisible({ timeout: 10000 }); - await button.click(); - - // Attendre que la modal soit visible - const modal = page.locator('[role="dialog"], .modal, [data-testid="upload-modal"], [data-testid="create-playlist-dialog"]').first(); - await expect(modal).toBeVisible({ timeout: 10000 }); - - console.log(`✅ [MODAL] Modal opened successfully`); -} - -/** - * Ferme une modal - * - * @param page - Page Playwright - * @returns Promise - */ -export async function closeModal(page: Page): Promise { - console.log(`📦 [MODAL] Closing modal...`); - - const closeButton = page - .locator('button:has-text("Fermer"), button:has-text("Close"), button[aria-label="Close"]') - .first(); - - if (await closeButton.isVisible().catch(() => false)) { - await closeButton.click(); - } - - // Attendre que la modal disparaisse - await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 5000 }); - - console.log(`✅ [MODAL] Modal closed successfully`); -} - -/** - * Remplit un champ de formulaire de manière robuste - * - * @param page - Page Playwright - * @param selector - Sélecteur du champ (ID, name, placeholder) - * @param value - Valeur à saisir - * @returns Promise - */ -export async function fillField( - page: Page, - selector: string, - value: string -): Promise { - console.log(`✏️ [FILL] Filling field ${selector} with value: ${value}`); - - const field = page.locator(selector).first(); - await expect(field).toBeVisible({ timeout: 10000 }); - await field.fill(value); - - console.log(`✅ [FILL] Field ${selector} filled successfully`); -} - -/** - * Attend que la liste/table soit chargée et contienne des données - * - * @param page - Page Playwright - * @param minRows - Nombre minimum de lignes attendues (défaut: 1, 0 pour accepter liste vide) - * @returns Promise - */ -export async function waitForListLoaded( - page: Page, - minRows: number = 1 -): Promise { - console.log(`📋 [LIST] Waiting for list/table to load (min ${minRows} rows)...`); - - // 🔴 CRITIQUE: Attendre que la page soit complètement chargée avant de chercher la liste - await page.waitForLoadState('domcontentloaded', { timeout: 10000 }).catch(() => { - console.warn('⚠️ [LIST] Timeout on domcontentloaded, continuing...'); - }); - - // Chercher différents types de listes: table, role="table", role="list", ou conteneur de liste - // Pour les playlists, on utilise role="list", pas table - // Pour la bibliothèque, peut être table OU grille de cards - const listSelectors = [ - 'table', - '[role="table"]', - '[role="list"]', - '.track-list', - '[aria-label*="playlist" i]', - '[aria-label*="list" i]', - '[data-testid="playlist-list"]', - '[data-testid="track-list"]', - // Pour la bibliothèque: grille de tracks - '[role="grid"]', - '.track-grid', - '[data-testid*="track"]', - ]; - - let list: Locator | null = null; - for (const selector of listSelectors) { - const candidate = page.locator(selector).first(); - if (await candidate.isVisible({ timeout: 2000 }).catch(() => false)) { - list = candidate; - break; - } - } - - // Si aucune liste trouvée, vérifier s'il y a un état vide (empty state) - if (!list) { - const emptyState = page.locator('text=/aucune|no.*found|empty|vide/i').first(); - if (await emptyState.isVisible({ timeout: 2000 }).catch(() => false)) { - console.log(`✅ [LIST] Empty state detected (no items to display)`); - return; - } - // Si minRows est 0, accepter qu'il n'y ait pas de liste visible (liste vide) - if (minRows === 0) { - console.log(`✅ [LIST] No list found but minRows=0, accepting empty state`); - return; - } - throw new Error(`Could not find list/table on page. Selectors tried: ${listSelectors.join(', ')}`); - } - - await expect(list).toBeVisible({ timeout: 10000 }); - - // Attendre que les lignes/éléments soient chargées - if (minRows > 0) { - // 🔴 FIX: Utiliser des sélecteurs larges qui fonctionnent pour tables ET cards/grids - const currentUrl = page.url(); - const isPlaylistsPage = currentUrl.includes('/playlists'); - - // Sélecteurs pour compter les éléments de liste (tables, cards, links, etc.) - const rowSelectors = [ - 'tr', // Table rows - '[role="row"]', // ARIA table rows - '[role="listitem"]', // ARIA list items - 'a[href^="/playlists/"]', // Playlist links (cards) - CRITICAL for playlists - '[role="list"] > a', // Links in lists - '[role="list"] > div', // Divs in lists (cards) - '[role="list"] > *', // Any direct children of lists - '.playlist-card', // Common class naming - '[class*="card"]', // Any element with "card" in class - '[class*="item"]', // Any element with "item" in class - '[data-testid="playlist-item"]', // Test ID - '[data-testid*="playlist"]', // Any playlist test ID - '[role="grid"] > *', // Grid items - ]; - - // Construire le locator avec tous les sélecteurs - const rows = page.locator(rowSelectors.join(', ')); - - const count = await rows.count(); - if (count < minRows) { - // Si on est sur la page playlists et qu'on ne trouve pas assez d'éléments, - // vérifier si la liste est en cours de chargement (skeleton visible) - if (isPlaylistsPage) { - const skeleton = page.locator('[role="list"] .skeleton, [data-testid*="skeleton"], [class*="skeleton"]').first(); - const isLoading = await skeleton.isVisible({ timeout: 2000 }).catch(() => false); - if (isLoading) { - // Attendre que le skeleton disparaisse - await skeleton.waitFor({ state: 'hidden', timeout: 10000 }).catch(() => { }); - // Recompter après que le skeleton disparaisse - const newCount = await rows.count(); - if (newCount >= minRows) { - console.log(`✅ [LIST] Found ${newCount} items after skeleton disappeared`); - return; - } - } - } - - // 🔴 FIX: Pour les pages playlists, être plus tolérant - // Si la liste existe mais qu'on ne trouve pas d'éléments, c'est peut-être juste vide ou en chargement - if (isPlaylistsPage && count === 0) { - // Vérifier que la liste/container existe au moins - const listExists = await list.isVisible({ timeout: 2000 }).catch(() => false); - if (listExists) { - // La liste existe, attendre un peu plus pour le chargement - await page.waitForTimeout(3000); - const retryCount = await rows.count(); - if (retryCount >= minRows) { - console.log(`✅ [LIST] Found ${retryCount} items after extended wait`); - return; - } - // Si toujours 0, vérifier s'il y a un état vide - const emptyState = page.locator('text=/aucune|no.*found|empty|vide/i').first(); - const isEmpty = await emptyState.isVisible({ timeout: 2000 }).catch(() => false); - if (isEmpty) { - console.log(`ℹ️ [LIST] List exists but is empty (empty state shown)`); - // Si minRows > 0 mais la liste est vide, c'est une erreur - if (minRows > 0) { - throw new Error(`Expected at least ${minRows} items but list is empty (empty state shown)`); - } - return; - } - } - } - - // Pour les autres pages ou si on n'est pas sur playlists, utiliser la logique standard - if (!isPlaylistsPage && count === 0 && minRows > 0) { - // Si on ne trouve rien, vérifier que la liste existe au moins - const listExists = await list.isVisible({ timeout: 2000 }).catch(() => false); - if (!listExists) { - throw new Error(`List/table not found on page. Expected at least ${minRows} items but found 0.`); - } - // Si la liste existe mais est vide, attendre un peu plus et réessayer - await page.waitForTimeout(2000); - const retryCount = await rows.count(); - if (retryCount >= minRows) { - console.log(`✅ [LIST] Found ${retryCount} items after retry`); - return; - } - } - - // Dernière tentative: vérifier le count exact - // 🔴 FIX: Pour les playlists, être très tolérant - si la liste existe, on considère que c'est OK - // Le vrai test de présence se fera avec getByText dans les tests - if (isPlaylistsPage) { - // Pour les playlists, si on arrive ici avec count=0, on a déjà vérifié que la liste existe - // Ne pas échouer ici - laisser les tests individuels vérifier avec getByText - console.warn(`⚠️ [LIST] Playlist page: Expected ${minRows} items but found ${count}. List container exists. Tests will verify with getByText.`); - return; // Sortir sans erreur - les tests vérifieront avec getByText - } else { - // Pour les autres pages (library, etc.), vérifier le count exact - await expect(rows).toHaveCount(minRows, { timeout: 15000 }); - } - } - } - - console.log(`✅ [LIST] List/table loaded with data`); -} diff --git a/apps/web/e2e/visual-complete.spec.ts b/apps/web/e2e/visual-complete.spec.ts deleted file mode 100644 index 4604e7b22..000000000 --- a/apps/web/e2e/visual-complete.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Suite complète de capture visuelle pour régression pixel-perfect. - * - * - Boucle sur URLs critiques (Login, Dashboard, Playlists, etc.) - * - Auth via storageState pour pages protégées ; pas d'auth pour login/register - * - Full page + screenshots ciblés (Sidebar, Player, Header) - * - waitForStableNetwork, masquage éléments dynamiques (dates, avatars) - * - Nommage : {screen-name}-desktop-dark.png - * - * Sortie : visual-tests/current/ (visual:capture) ou visual-tests/baselines/ (visual:update) - */ - -import { test } from '@playwright/test'; -import fs from 'fs'; -import path from 'path'; -import { visualOutputDir, screenshotName } from '../playwright.config.visual'; - -const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || process.env.VITE_FRONTEND_URL || 'http://localhost:5173'; -const ANIMATION_SETTLE_MS = 800; -const NETWORK_IDLE_MS = 500; - -/** Désactive les animations/transitions CSS pour captures stables */ -async function disableAnimations(page: import('@playwright/test').Page) { - await page.addStyleTag({ - content: ` - *, *::before, *::after { - animation-duration: 0s !important; - animation-delay: 0s !important; - transition-duration: 0s !important; - transition-delay: 0s !important; - } - `, - }); -} - -/** Force le thème sombre sur le document */ -async function ensureDarkTheme(page: import('@playwright/test').Page) { - await page.evaluate(() => { - document.documentElement.classList.add('dark'); - document.documentElement.setAttribute('data-theme', 'dark'); - }); - await page.waitForTimeout(100); -} - -/** Attend réseau inactif puis un court délai pour éviter skeletons / images en cours de chargement */ -async function waitForStableNetwork(page: import('@playwright/test').Page) { - await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {}); - await page.waitForTimeout(NETWORK_IDLE_MS); -} - -/** Locators des éléments dynamiques à masquer (dates, avatars, temps) */ -async function getDynamicMasks(page: import('@playwright/test').Page): Promise { - const candidates = [ - page.locator('img[alt="Avatar"], img[alt*="avatar"]').first(), - page.locator('[role="timer"]').first(), - page.locator('time').first(), - ]; - const out: import('@playwright/test').Locator[] = []; - for (const loc of candidates) { - if ((await loc.count()) > 0) out.push(loc); - } - return out; -} - -/** Écrit un screenshot dans visual-tests/current ou baselines */ -async function saveScreenshot( - page: import('@playwright/test').Page, - name: string, - options: { fullPage?: boolean; locator?: import('@playwright/test').Locator } = {} -) { - fs.mkdirSync(visualOutputDir, { recursive: true }); - const filePath = path.join(visualOutputDir, screenshotName(name)); - const mask = await getDynamicMasks(page); - const screenshotOpts = { path: filePath, mask: mask.length > 0 ? mask : undefined }; - - if (options.locator) { - await options.locator.screenshot(screenshotOpts); - } else { - await page.screenshot({ fullPage: options.fullPage ?? true, ...screenshotOpts }); - } - test.info().attach(name, { path: filePath, contentType: 'image/png' }); -} - -const SCREENS: Array<{ - name: string; - url: string; - auth: boolean; - full: boolean; - locator?: string; -}> = [ - { name: 'login', url: '/login', auth: false, full: true }, - { name: 'register', url: '/register', auth: false, full: true }, - { name: 'dashboard', url: '/dashboard', auth: true, full: true }, - { name: 'playlists', url: '/playlists', auth: true, full: true }, - { name: 'library', url: '/library', auth: true, full: true }, - { name: '404', url: '/non-existent-route-404', auth: false, full: true }, -]; - -const COMPONENT_CAPTURES: Array<{ name: string; url: string; locator: string }> = [ - { name: 'sidebar', url: '/dashboard', locator: '[data-testid="app-sidebar"]' }, - { name: 'header', url: '/dashboard', locator: 'header' }, - { name: 'player', url: '/dashboard', locator: '[data-testid="global-player"]' }, -]; - -test.describe('Visual capture (complete)', () => { - test.beforeEach(async ({ page }) => { - await page.emulateMedia({ reducedMotion: 'reduce', colorScheme: 'dark' }); - }); - - test.describe('Full-page screens (no auth)', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - for (const screen of SCREENS.filter((s) => !s.auth)) { - test(`${screen.name}`, async ({ page }) => { - const url = BASE_URL.replace(/\/$/, '') + screen.url; - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 }); - await waitForStableNetwork(page); - await page.waitForSelector('body', { timeout: 8000 }).catch(() => {}); - - await disableAnimations(page); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await saveScreenshot(page, screen.name, { fullPage: true }); - }); - } - }); - - test.describe('Full-page screens (authenticated)', () => { - for (const screen of SCREENS.filter((s) => s.auth)) { - test(`${screen.name}`, async ({ page }) => { - const url = BASE_URL.replace(/\/$/, '') + screen.url; - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 }); - await waitForStableNetwork(page); - await page.waitForSelector('body', { timeout: 8000 }).catch(() => {}); - - await disableAnimations(page); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - await saveScreenshot(page, screen.name, { fullPage: true }); - }); - } - }); - - test.describe('Component screenshots (authenticated)', () => { - for (const comp of COMPONENT_CAPTURES) { - test(comp.name, async ({ page }) => { - const url = BASE_URL.replace(/\/$/, '') + comp.url; - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 20000 }); - await waitForStableNetwork(page); - - const loc = page.locator(comp.locator).first(); - const visible = await loc.waitFor({ state: 'visible', timeout: 10000 }).then(() => true).catch(() => false); - if (!visible) { - test.skip(true, `${comp.name} locator not visible`); - return; - } - - await disableAnimations(page); - await ensureDarkTheme(page); - await page.waitForTimeout(ANIMATION_SETTLE_MS); - - const mask = await getDynamicMasks(page); - fs.mkdirSync(visualOutputDir, { recursive: true }); - const filePath = path.join(visualOutputDir, screenshotName(comp.name)); - await loc.screenshot({ path: filePath, mask: mask.length > 0 ? mask : undefined }); - test.info().attach(comp.name, { path: filePath, contentType: 'image/png' }); - }); - } - }); -}); diff --git a/apps/web/e2e/visual-regression.spec.ts b/apps/web/e2e/visual-regression.spec.ts deleted file mode 100644 index d7f13354f..000000000 --- a/apps/web/e2e/visual-regression.spec.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { loginAsUser, TEST_CONFIG } from './utils/test-helpers'; - -/** - * Visual Regression Tests - * - * These tests capture screenshots of UI components and pages - * to detect visual regressions. Screenshots are stored in: - * - test-results/visual-regression.spec.ts-snapshots/ - * - * To update screenshots after intentional changes: - * - Run: npx playwright test --update-snapshots - * - * To run only visual tests: - * - Run: npx playwright test visual-regression - */ - -test.describe('Visual Regression Tests', () => { - // Use authenticated state for most tests - test.use({ storageState: 'e2e/.auth/user.json' }); - - test.describe('Authentication Pages', () => { - test('login page visual snapshot', async ({ page }) => { - // Use unauthenticated state for login page - await page.context().clearCookies(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); - await page.waitForLoadState('networkidle'); - - // Wait for form to be fully rendered - await page.waitForSelector('form', { timeout: 5000 }); - await page.waitForTimeout(500); // Allow animations to settle - - await expect(page).toHaveScreenshot('login-page.png', { - fullPage: true, - maxDiffPixels: 100, // Allow small differences (fonts, anti-aliasing) - }); - }); - - test('register page visual snapshot', async ({ page }) => { - // Use unauthenticated state for register page - await page.context().clearCookies(); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); - await page.waitForLoadState('networkidle'); - - // Wait for form to be fully rendered - await page.waitForSelector('form', { timeout: 5000 }); - await page.waitForTimeout(500); - - await expect(page).toHaveScreenshot('register-page.png', { - fullPage: true, - maxDiffPixels: 100, - }); - }); - }); - - test.describe('Dashboard Pages', () => { - test('dashboard page visual snapshot', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Wait for main content to load - await page.waitForSelector('main, [role="main"]', { timeout: 10000 }); - await page.waitForTimeout(1000); // Allow data to load - - await expect(page).toHaveScreenshot('dashboard-page.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - - test('dashboard header visual snapshot', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Wait for header - const header = page.locator('header').first(); - await header.waitFor({ timeout: 5000 }); - await page.waitForTimeout(500); - - await expect(header).toHaveScreenshot('dashboard-header.png', { - maxDiffPixels: 50, - }); - }); - - test('dashboard sidebar visual snapshot', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - - // Wait for sidebar - const sidebar = page.locator('aside').first(); - await sidebar.waitFor({ timeout: 5000 }); - await page.waitForTimeout(500); - - await expect(sidebar).toHaveScreenshot('dashboard-sidebar.png', { - maxDiffPixels: 50, - }); - }); - }); - - test.describe('Profile Page', () => { - test('profile page visual snapshot', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); - await page.waitForLoadState('networkidle'); - - // Wait for profile content - await page.waitForSelector('main, [role="main"]', { timeout: 10000 }); - await page.waitForTimeout(1000); - - await expect(page).toHaveScreenshot('profile-page.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - }); - - test.describe('Tracks Pages', () => { - test('tracks list page visual snapshot', async ({ page }) => { - // Navigate to tracks page (adjust route as needed) - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/tracks`); - await page.waitForLoadState('networkidle'); - - // Wait for tracks list to load - await page.waitForTimeout(2000); // Allow tracks to load - - await expect(page).toHaveScreenshot('tracks-list-page.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - }); - - test.describe('Playlists Pages', () => { - test('playlists page visual snapshot', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`); - await page.waitForLoadState('networkidle'); - - // Wait for playlists to load - await page.waitForTimeout(2000); - - await expect(page).toHaveScreenshot('playlists-page.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - }); - - test.describe('UI Components', () => { - test('button variants visual snapshot', async ({ page }) => { - // Create a test page with button variants - await page.setContent(` - - - - - - -
- - - - - -
- - - `); - - await page.waitForTimeout(500); - - await expect(page).toHaveScreenshot('button-variants.png', { - maxDiffPixels: 50, - }); - }); - - test('card component visual snapshot', async ({ page }) => { - await page.setContent(` - - - - - - -
-

Card Title

-

This is a card component with some content.

-
- - - `); - - await page.waitForTimeout(500); - - await expect(page).toHaveScreenshot('card-component.png', { - maxDiffPixels: 50, - }); - }); - - test('form elements visual snapshot', async ({ page }) => { - await page.setContent(` - - - - - - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- - - `); - - await page.waitForTimeout(500); - - await expect(page).toHaveScreenshot('form-elements.png', { - maxDiffPixels: 50, - }); - }); - }); - - test.describe('Error States', () => { - test('404 page visual snapshot', async ({ page }) => { - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-page`); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - await expect(page).toHaveScreenshot('404-page.png', { - fullPage: true, - maxDiffPixels: 100, - }); - }); - }); - - test.describe('Responsive Design', () => { - test('mobile viewport dashboard snapshot', async ({ page }) => { - // Set mobile viewport - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - await expect(page).toHaveScreenshot('dashboard-mobile.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - - test('tablet viewport dashboard snapshot', async ({ page }) => { - // Set tablet viewport - await page.setViewportSize({ width: 768, height: 1024 }); - - await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - await expect(page).toHaveScreenshot('dashboard-tablet.png', { - fullPage: true, - maxDiffPixels: 200, - }); - }); - }); -}); - diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-chromium-linux.png deleted file mode 100644 index a85aa2f99..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-firefox-linux.png deleted file mode 100644 index d7181e8be..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-msedge-linux.png deleted file mode 100644 index 42c172726..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/404-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-chromium-linux.png deleted file mode 100644 index 8dfa60d30..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-firefox-linux.png deleted file mode 100644 index 8a37f7b6c..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-msedge-linux.png deleted file mode 100644 index 8dfa60d30..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/button-variants-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-chromium-linux.png deleted file mode 100644 index 885306b34..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-firefox-linux.png deleted file mode 100644 index d84064f10..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-msedge-linux.png deleted file mode 100644 index 885306b34..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/card-component-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-chromium-linux.png deleted file mode 100644 index 842d48ba6..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-firefox-linux.png deleted file mode 100644 index 06ce85792..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-msedge-linux.png deleted file mode 100644 index 0a8a236f6..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-mobile-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-chromium-linux.png deleted file mode 100644 index 886998600..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-firefox-linux.png deleted file mode 100644 index 50384671e..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-msedge-linux.png deleted file mode 100644 index 400c64cc4..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/dashboard-tablet-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-chromium-linux.png deleted file mode 100644 index 9feb33b5d..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-firefox-linux.png deleted file mode 100644 index 878e5b772..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-msedge-linux.png deleted file mode 100644 index 9feb33b5d..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/form-elements-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-chromium-linux.png deleted file mode 100644 index a85aa2f99..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-firefox-linux.png deleted file mode 100644 index 0cbb39fb3..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-msedge-linux.png deleted file mode 100644 index 113138925..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/login-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-chromium-linux.png deleted file mode 100644 index a85aa2f99..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-firefox-linux.png deleted file mode 100644 index 0cbb39fb3..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-msedge-linux.png deleted file mode 100644 index 113138925..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/playlists-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-chromium-linux.png deleted file mode 100644 index a85aa2f99..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-firefox-linux.png deleted file mode 100644 index 0cbb39fb3..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-msedge-linux.png deleted file mode 100644 index 113138925..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/register-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-chromium-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-chromium-linux.png deleted file mode 100644 index a85aa2f99..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-chromium-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-firefox-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-firefox-linux.png deleted file mode 100644 index d7181e8be..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-firefox-linux.png and /dev/null differ diff --git a/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-msedge-linux.png b/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-msedge-linux.png deleted file mode 100644 index 42c172726..000000000 Binary files a/apps/web/e2e/visual-regression.spec.ts-snapshots/tracks-list-page-msedge-linux.png and /dev/null differ diff --git a/apps/web/playwright-report/index.html b/apps/web/playwright-report/index.html index a0efc35c4..ce68e0594 100644 --- a/apps/web/playwright-report/index.html +++ b/apps/web/playwright-report/index.html @@ -82,4 +82,4 @@ Error generating stack: `+a.message+`
- \ No newline at end of file + \ No newline at end of file diff --git a/apps/web/playwright-report/trace/assets/codeMirrorModule-a5XoALAZ.js b/apps/web/playwright-report/trace/assets/codeMirrorModule-a5XoALAZ.js deleted file mode 100644 index 67c257fe2..000000000 --- a/apps/web/playwright-report/trace/assets/codeMirrorModule-a5XoALAZ.js +++ /dev/null @@ -1,32 +0,0 @@ -import{v as Ju}from"./defaultSettingsView-CJSZINFr.js";var vi={exports:{}},Zu=vi.exports,pa;function mt(){return pa||(pa=1,(function(ct,xt){(function(b,pe){ct.exports=pe()})(Zu,(function(){var b=navigator.userAgent,pe=navigator.platform,_=/gecko\/\d/i.test(b),te=/MSIE \d/.test(b),oe=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(b),Q=/Edge\/(\d+)/.exec(b),k=te||oe||Q,I=k&&(te?document.documentMode||6:+(Q||oe)[1]),Y=!Q&&/WebKit\//.test(b),ne=Y&&/Qt\/\d+\.\d+/.test(b),S=!Q&&/Chrome\/(\d+)/.exec(b),R=S&&+S[1],A=/Opera\//.test(b),V=/Apple Computer/.test(navigator.vendor),ue=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(b),O=/PhantomJS/.test(b),w=V&&(/Mobile\/\w+/.test(b)||navigator.maxTouchPoints>2),M=/Android/.test(b),N=w||M||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(b),z=w||/Mac/.test(pe),X=/\bCrOS\b/.test(b),q=/win/i.test(pe),p=A&&b.match(/Version\/(\d*\.\d*)/);p&&(p=Number(p[1])),p&&p>=15&&(A=!1,Y=!0);var W=z&&(ne||A&&(p==null||p<12.11)),J=_||k&&I>=9;function P(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var $=function(e,t){var n=e.className,r=P(t).exec(n);if(r){var i=n.slice(r.index+r[0].length);e.className=n.slice(0,r.index)+(i?r[1]+i:"")}};function F(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function G(e,t){return F(e).appendChild(t)}function c(e,t,n,r){var i=document.createElement(e);if(n&&(i.className=n),r&&(i.style.cssText=r),typeof t=="string")i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o=t)return l+(t-o);l+=a-o,l+=n-l%n,o=a+1}}var Ce=function(){this.id=null,this.f=null,this.time=0,this.handler=xe(this.onTimeout,this)};Ce.prototype.onTimeout=function(e){e.id=0,e.time<=+new Date?e.f():setTimeout(e.handler,e.time-+new Date)},Ce.prototype.set=function(e,t){this.f=t;var n=+new Date+e;(!this.id||n=t)return r+Math.min(l,t-i);if(i+=o-r,i+=n-i%n,r=o+1,i>=t)return r}}var Ue=[""];function et(e){for(;Ue.length<=e;)Ue.push(we(Ue)+" ");return Ue[e]}function we(e){return e[e.length-1]}function Ie(e,t){for(var n=[],r=0;r"€"&&(e.toUpperCase()!=e.toLowerCase()||ze.test(e))}function De(e,t){return t?t.source.indexOf("\\w")>-1&&me(e)?!0:t.test(e):me(e)}function be(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}var Be=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function Ne(e){return e.charCodeAt(0)>=768&&Be.test(e)}function Mt(e,t,n){for(;(n<0?t>0:tn?-1:1;;){if(t==n)return t;var i=(t+n)/2,o=r<0?Math.ceil(i):Math.floor(i);if(o==t)return e(o)?t:n;e(o)?n=o:t=o+r}}function or(e,t,n,r){if(!e)return r(t,n,"ltr",0);for(var i=!1,o=0;ot||t==n&&l.to==t)&&(r(Math.max(l.from,t),Math.min(l.to,n),l.level==1?"rtl":"ltr",o),i=!0)}i||r(t,n,"ltr")}var br=null;function lr(e,t,n){var r;br=null;for(var i=0;it)return i;o.to==t&&(o.from!=o.to&&n=="before"?r=i:br=i),o.from==t&&(o.from!=o.to&&n!="before"?r=i:br=i)}return r??br}var mi=(function(){var e="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",t="nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111";function n(u){return u<=247?e.charAt(u):1424<=u&&u<=1524?"R":1536<=u&&u<=1785?t.charAt(u-1536):1774<=u&&u<=2220?"r":8192<=u&&u<=8203?"w":u==8204?"b":"L"}var r=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,i=/[stwN]/,o=/[LRr]/,l=/[Lb1n]/,a=/[1n]/;function s(u,h,x){this.level=u,this.from=h,this.to=x}return function(u,h){var x=h=="ltr"?"L":"R";if(u.length==0||h=="ltr"&&!r.test(u))return!1;for(var D=u.length,L=[],H=0;H-1&&(r[t]=i.slice(0,o).concat(i.slice(o+1)))}}}function Ye(e,t){var n=Zt(e,t);if(n.length)for(var r=Array.prototype.slice.call(arguments,2),i=0;i0}function Bt(e){e.prototype.on=function(t,n){Se(this,t,n)},e.prototype.off=function(t,n){ht(this,t,n)}}function pt(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function Er(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function kt(e){return e.defaultPrevented!=null?e.defaultPrevented:e.returnValue==!1}function ar(e){pt(e),Er(e)}function ln(e){return e.target||e.srcElement}function Rt(e){var t=e.which;return t==null&&(e.button&1?t=1:e.button&2?t=3:e.button&4&&(t=2)),z&&e.ctrlKey&&t==1&&(t=3),t}var xi=(function(){if(k&&I<9)return!1;var e=c("div");return"draggable"in e||"dragDrop"in e})(),Or;function Rn(e){if(Or==null){var t=c("span","​");G(e,c("span",[t,document.createTextNode("x")])),e.firstChild.offsetHeight!=0&&(Or=t.offsetWidth<=1&&t.offsetHeight>2&&!(k&&I<8))}var n=Or?c("span","​"):c("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");return n.setAttribute("cm-text",""),n}var an;function sr(e){if(an!=null)return an;var t=G(e,document.createTextNode("AخA")),n=C(t,0,1).getBoundingClientRect(),r=C(t,1,2).getBoundingClientRect();return F(e),!n||n.left==n.right?!1:an=r.right-n.right<3}var zt=` - -b`.split(/\n/).length!=3?function(e){for(var t=0,n=[],r=e.length;t<=r;){var i=e.indexOf(` -`,t);i==-1&&(i=e.length);var o=e.slice(t,e.charAt(i-1)=="\r"?i-1:i),l=o.indexOf("\r");l!=-1?(n.push(o.slice(0,l)),t+=l+1):(n.push(o),t=i+1)}return n}:function(e){return e.split(/\r\n?|\n/)},ur=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch{return!1}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch{}return!t||t.parentElement()!=e?!1:t.compareEndPoints("StartToEnd",t)!=0},Wn=(function(){var e=c("div");return"oncopy"in e?!0:(e.setAttribute("oncopy","return;"),typeof e.oncopy=="function")})(),Wt=null;function yi(e){if(Wt!=null)return Wt;var t=G(e,c("span","x")),n=t.getBoundingClientRect(),r=C(t,0,1).getBoundingClientRect();return Wt=Math.abs(n.left-r.left)>1}var Pr={},Ht={};function _t(e,t){arguments.length>2&&(t.dependencies=Array.prototype.slice.call(arguments,2)),Pr[e]=t}function kr(e,t){Ht[e]=t}function Ir(e){if(typeof e=="string"&&Ht.hasOwnProperty(e))e=Ht[e];else if(e&&typeof e.name=="string"&&Ht.hasOwnProperty(e.name)){var t=Ht[e.name];typeof t=="string"&&(t={name:t}),e=K(t,e),e.name=t.name}else{if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e))return Ir("application/xml");if(typeof e=="string"&&/^[\w\-]+\/[\w\-]+\+json$/.test(e))return Ir("application/json")}return typeof e=="string"?{name:e}:e||{name:"null"}}function zr(e,t){t=Ir(t);var n=Pr[t.name];if(!n)return zr(e,"text/plain");var r=n(e,t);if(fr.hasOwnProperty(t.name)){var i=fr[t.name];for(var o in i)i.hasOwnProperty(o)&&(r.hasOwnProperty(o)&&(r["_"+o]=r[o]),r[o]=i[o])}if(r.name=t.name,t.helperType&&(r.helperType=t.helperType),t.modeProps)for(var l in t.modeProps)r[l]=t.modeProps[l];return r}var fr={};function Br(e,t){var n=fr.hasOwnProperty(e)?fr[e]:fr[e]={};Me(t,n)}function Gt(e,t){if(t===!0)return t;if(e.copyState)return e.copyState(t);var n={};for(var r in t){var i=t[r];i instanceof Array&&(i=i.concat([])),n[r]=i}return n}function sn(e,t){for(var n;e.innerMode&&(n=e.innerMode(t),!(!n||n.mode==e));)t=n.state,e=n.mode;return n||{mode:e,state:t}}function Rr(e,t,n){return e.startState?e.startState(t,n):!0}var Je=function(e,t,n){this.pos=this.start=0,this.string=e,this.tabSize=t||8,this.lastColumnPos=this.lastColumnValue=0,this.lineStart=0,this.lineOracle=n};Je.prototype.eol=function(){return this.pos>=this.string.length},Je.prototype.sol=function(){return this.pos==this.lineStart},Je.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},Je.prototype.next=function(){if(this.post},Je.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},Je.prototype.skipToEnd=function(){this.pos=this.string.length},Je.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1)return this.pos=t,!0},Je.prototype.backUp=function(e){this.pos-=e},Je.prototype.column=function(){return this.lastColumnPos0?null:(o&&t!==!1&&(this.pos+=o[0].length),o)}},Je.prototype.current=function(){return this.string.slice(this.start,this.pos)},Je.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}},Je.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)},Je.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};function ye(e,t){if(t-=e.first,t<0||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],o=i.chunkSize();if(t=e.first&&tn?B(n,ye(e,n).text.length):Za(t,ye(e,t.line).text.length)}function Za(e,t){var n=e.ch;return n==null||n>t?B(e.line,t):n<0?B(e.line,0):e}function vo(e,t){for(var n=[],r=0;rthis.maxLookAhead&&(this.maxLookAhead=e),t},Xt.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},Xt.prototype.nextLine=function(){this.line++,this.maxLookAhead>0&&this.maxLookAhead--},Xt.fromSaved=function(e,t,n){return t instanceof Hn?new Xt(e,Gt(e.mode,t.state),n,t.lookAhead):new Xt(e,Gt(e.mode,t),n)},Xt.prototype.save=function(e){var t=e!==!1?Gt(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new Hn(t,this.maxLookAhead):t};function mo(e,t,n,r){var i=[e.state.modeGen],o={};So(e,t.text,e.doc.mode,n,function(u,h){return i.push(u,h)},o,r);for(var l=n.state,a=function(u){n.baseTokens=i;var h=e.state.overlays[u],x=1,D=0;n.state=!0,So(e,t.text,h.mode,n,function(L,H){for(var Z=x;DL&&i.splice(x,1,L,i[x+1],ie),x+=2,D=Math.min(L,ie)}if(H)if(h.opaque)i.splice(Z,x-Z,L,"overlay "+H),x=Z+2;else for(;Ze.options.maxHighlightLength&&Gt(e.doc.mode,r.state),o=mo(e,t,r);i&&(r.state=i),t.stateAfter=r.save(!i),t.styles=o.styles,o.classes?t.styleClasses=o.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))}return t.styles}function fn(e,t,n){var r=e.doc,i=e.display;if(!r.mode.startState)return new Xt(r,!0,t);var o=Va(e,t,n),l=o>r.first&&ye(r,o-1).stateAfter,a=l?Xt.fromSaved(r,l,o):new Xt(r,Rr(r.mode),o);return r.iter(o,t,function(s){bi(e,s.text,a);var u=a.line;s.stateAfter=u==t-1||u%5==0||u>=i.viewFrom&&ut.start)return o}throw new Error("Mode "+e.name+" failed to advance stream.")}var bo=function(e,t,n){this.start=e.start,this.end=e.pos,this.string=e.current(),this.type=t||null,this.state=n};function ko(e,t,n,r){var i=e.doc,o=i.mode,l;t=Ae(i,t);var a=ye(i,t.line),s=fn(e,t.line,n),u=new Je(a.text,e.options.tabSize,s),h;for(r&&(h=[]);(r||u.pose.options.maxHighlightLength?(a=!1,l&&bi(e,t,r,h.pos),h.pos=t.length,x=null):x=wo(ki(n,h,r.state,D),o),D){var L=D[0].name;L&&(x="m-"+(x?L+" "+x:L))}if(!a||u!=x){for(;sl;--a){if(a<=o.first)return o.first;var s=ye(o,a-1),u=s.stateAfter;if(u&&(!n||a+(u instanceof Hn?u.lookAhead:0)<=o.modeFrontier))return a;var h=Fe(s.text,null,e.options.tabSize);(i==null||r>h)&&(i=a-1,r=h)}return i}function $a(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontiern;r--){var i=ye(e,r).stateAfter;if(i&&(!(i instanceof Hn)||r+i.lookAhead=t:o.to>t);(r||(r=[])).push(new _n(l,o.from,s?null:o.to))}}return r}function os(e,t,n){var r;if(e)for(var i=0;i=t:o.to>t);if(a||o.from==t&&l.type=="bookmark"&&(!n||o.marker.insertLeft)){var s=o.from==null||(l.inclusiveLeft?o.from<=t:o.from0&&a)for(var ge=0;ge0)){var h=[s,1],x=ce(u.from,a.from),D=ce(u.to,a.to);(x<0||!l.inclusiveLeft&&!x)&&h.push({from:u.from,to:a.from}),(D>0||!l.inclusiveRight&&!D)&&h.push({from:a.to,to:u.to}),i.splice.apply(i,h),s+=h.length-3}}return i}function Co(e){var t=e.markedSpans;if(t){for(var n=0;nt)&&(!r||Si(r,o.marker)<0)&&(r=o.marker)}return r}function Ao(e,t,n,r,i){var o=ye(e,t),l=$t&&o.markedSpans;if(l)for(var a=0;a=0&&x<=0||h<=0&&x>=0)&&(h<=0&&(s.marker.inclusiveRight&&i.inclusiveLeft?ce(u.to,n)>=0:ce(u.to,n)>0)||h>=0&&(s.marker.inclusiveRight&&i.inclusiveLeft?ce(u.from,r)<=0:ce(u.from,r)<0)))return!0}}}function qt(e){for(var t;t=Fo(e);)e=t.find(-1,!0).line;return e}function ss(e){for(var t;t=Kn(e);)e=t.find(1,!0).line;return e}function us(e){for(var t,n;t=Kn(e);)e=t.find(1,!0).line,(n||(n=[])).push(e);return n}function Li(e,t){var n=ye(e,t),r=qt(n);return n==r?t:f(r)}function No(e,t){if(t>e.lastLine())return t;var n=ye(e,t),r;if(!cr(e,n))return t;for(;r=Kn(n);)n=r.find(1,!0).line;return f(n)+1}function cr(e,t){var n=$t&&t.markedSpans;if(n){for(var r=void 0,i=0;it.maxLineLength&&(t.maxLineLength=i,t.maxLine=r)})}var Hr=function(e,t,n){this.text=e,Do(this,t),this.height=n?n(this):1};Hr.prototype.lineNo=function(){return f(this)},Bt(Hr);function fs(e,t,n,r){e.text=t,e.stateAfter&&(e.stateAfter=null),e.styles&&(e.styles=null),e.order!=null&&(e.order=null),Co(e),Do(e,n);var i=r?r(e):1;i!=e.height&&Et(e,i)}function cs(e){e.parent=null,Co(e)}var ds={},hs={};function Eo(e,t){if(!e||/^\s*$/.test(e))return null;var n=t.addModeClass?hs:ds;return n[e]||(n[e]=e.replace(/\S+/g,"cm-$&"))}function Oo(e,t){var n=T("span",null,null,Y?"padding-right: .1px":null),r={pre:T("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var o=i?t.rest[i-1]:t.line,l=void 0;r.pos=0,r.addToken=gs,sr(e.display.measure)&&(l=Re(o,e.doc.direction))&&(r.addToken=ms(r.addToken,l)),r.map=[];var a=t!=e.display.externalMeasured&&f(o);xs(o,r,xo(e,o,a)),o.styleClasses&&(o.styleClasses.bgClass&&(r.bgClass=de(o.styleClasses.bgClass,r.bgClass||"")),o.styleClasses.textClass&&(r.textClass=de(o.styleClasses.textClass,r.textClass||""))),r.map.length==0&&r.map.push(0,0,r.content.appendChild(Rn(e.display.measure))),i==0?(t.measure.map=r.map,t.measure.cache={}):((t.measure.maps||(t.measure.maps=[])).push(r.map),(t.measure.caches||(t.measure.caches=[])).push({}))}if(Y){var s=r.content.lastChild;(/\bcm-tab\b/.test(s.className)||s.querySelector&&s.querySelector(".cm-tab"))&&(r.content.className="cm-tab-wrap-hack")}return Ye(e,"renderLine",e,t.line,r.pre),r.pre.className&&(r.textClass=de(r.pre.className,r.textClass||"")),r}function ps(e){var t=c("span","•","cm-invalidchar");return t.title="\\u"+e.charCodeAt(0).toString(16),t.setAttribute("aria-label",t.title),t}function gs(e,t,n,r,i,o,l){if(t){var a=e.splitSpaces?vs(t,e.trailingSpace):t,s=e.cm.state.specialChars,u=!1,h;if(!s.test(t))e.col+=t.length,h=document.createTextNode(a),e.map.push(e.pos,e.pos+t.length,h),k&&I<9&&(u=!0),e.pos+=t.length;else{h=document.createDocumentFragment();for(var x=0;;){s.lastIndex=x;var D=s.exec(t),L=D?D.index-x:t.length-x;if(L){var H=document.createTextNode(a.slice(x,x+L));k&&I<9?h.appendChild(c("span",[H])):h.appendChild(H),e.map.push(e.pos,e.pos+L,H),e.col+=L,e.pos+=L}if(!D)break;x+=L+1;var Z=void 0;if(D[0]==" "){var ie=e.cm.options.tabSize,ae=ie-e.col%ie;Z=h.appendChild(c("span",et(ae),"cm-tab")),Z.setAttribute("role","presentation"),Z.setAttribute("cm-text"," "),e.col+=ae}else D[0]=="\r"||D[0]==` -`?(Z=h.appendChild(c("span",D[0]=="\r"?"␍":"␤","cm-invalidchar")),Z.setAttribute("cm-text",D[0]),e.col+=1):(Z=e.cm.options.specialCharPlaceholder(D[0]),Z.setAttribute("cm-text",D[0]),k&&I<9?h.appendChild(c("span",[Z])):h.appendChild(Z),e.col+=1);e.map.push(e.pos,e.pos+1,Z),e.pos++}}if(e.trailingSpace=a.charCodeAt(t.length-1)==32,n||r||i||u||o||l){var he=n||"";r&&(he+=r),i&&(he+=i);var se=c("span",[h],he,o);if(l)for(var ge in l)l.hasOwnProperty(ge)&&ge!="style"&&ge!="class"&&se.setAttribute(ge,l[ge]);return e.content.appendChild(se)}e.content.appendChild(h)}}function vs(e,t){if(e.length>1&&!/ /.test(e))return e;for(var n=t,r="",i=0;iu&&x.from<=u));D++);if(x.to>=h)return e(n,r,i,o,l,a,s);e(n,r.slice(0,x.to-u),i,o,null,a,s),o=null,r=r.slice(x.to-u),u=x.to}}}function Po(e,t,n,r){var i=!r&&n.widgetNode;i&&e.map.push(e.pos,e.pos+t,i),!r&&e.cm.display.input.needsContentAttribute&&(i||(i=e.content.appendChild(document.createElement("span"))),i.setAttribute("cm-marker",n.id)),i&&(e.cm.display.input.setUneditable(i),e.content.appendChild(i)),e.pos+=t,e.trailingSpace=!1}function xs(e,t,n){var r=e.markedSpans,i=e.text,o=0;if(!r){for(var l=1;ls||Ee.collapsed&&ke.to==s&&ke.from==s)){if(ke.to!=null&&ke.to!=s&&L>ke.to&&(L=ke.to,Z=""),Ee.className&&(H+=" "+Ee.className),Ee.css&&(D=(D?D+";":"")+Ee.css),Ee.startStyle&&ke.from==s&&(ie+=" "+Ee.startStyle),Ee.endStyle&&ke.to==L&&(ge||(ge=[])).push(Ee.endStyle,ke.to),Ee.title&&((he||(he={})).title=Ee.title),Ee.attributes)for(var Ke in Ee.attributes)(he||(he={}))[Ke]=Ee.attributes[Ke];Ee.collapsed&&(!ae||Si(ae.marker,Ee)<0)&&(ae=ke)}else ke.from>s&&L>ke.from&&(L=ke.from)}if(ge)for(var st=0;st=a)break;for(var Nt=Math.min(a,L);;){if(h){var Tt=s+h.length;if(!ae){var tt=Tt>Nt?h.slice(0,Nt-s):h;t.addToken(t,tt,x?x+H:H,ie,s+tt.length==L?Z:"",D,he)}if(Tt>=Nt){h=h.slice(Nt-s),s=Nt;break}s=Tt,ie=""}h=i.slice(o,o=n[u++]),x=Eo(n[u++],t.cm.options)}}}function Io(e,t,n){this.line=t,this.rest=us(t),this.size=this.rest?f(we(this.rest))-n+1:1,this.node=this.text=null,this.hidden=cr(e,t)}function Gn(e,t,n){for(var r=[],i,o=t;o2&&o.push((s.bottom+u.top)/2-n.top)}}o.push(n.bottom-n.top)}}function qo(e,t,n){if(e.line==t)return{map:e.measure.map,cache:e.measure.cache};if(e.rest){for(var r=0;rn)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}}function Fs(e,t){t=qt(t);var n=f(t),r=e.display.externalMeasured=new Io(e.doc,t,n);r.lineN=n;var i=r.built=Oo(e,r);return r.text=i.pre,G(e.display.lineMeasure,i.pre),r}function jo(e,t,n,r){return Qt(e,qr(e,t),n,r)}function Ai(e,t){if(t>=e.display.viewFrom&&t=n.lineN&&tt)&&(o=s-a,i=o-1,t>=s&&(l="right")),i!=null){if(r=e[u+2],a==s&&n==(r.insertLeft?"left":"right")&&(l=n),n=="left"&&i==0)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)r=e[(u-=3)+2],l="left";if(n=="right"&&i==s-a)for(;u=0&&(n=e[i]).left==n.right;i--);return n}function Ns(e,t,n,r){var i=Uo(t.map,n,r),o=i.node,l=i.start,a=i.end,s=i.collapse,u;if(o.nodeType==3){for(var h=0;h<4;h++){for(;l&&Ne(t.line.text.charAt(i.coverStart+l));)--l;for(;i.coverStart+a0&&(s=r="right");var x;e.options.lineWrapping&&(x=o.getClientRects()).length>1?u=x[r=="right"?x.length-1:0]:u=o.getBoundingClientRect()}if(k&&I<9&&!l&&(!u||!u.left&&!u.right)){var D=o.parentNode.getClientRects()[0];D?u={left:D.left,right:D.left+Kr(e.display),top:D.top,bottom:D.bottom}:u=Ko}for(var L=u.top-t.rect.top,H=u.bottom-t.rect.top,Z=(L+H)/2,ie=t.view.measure.heights,ae=0;ae=r.text.length?(s=r.text.length,u="before"):s<=0&&(s=0,u="after"),!a)return l(u=="before"?s-1:s,u=="before");function h(H,Z,ie){var ae=a[Z],he=ae.level==1;return l(ie?H-1:H,he!=ie)}var x=lr(a,s,u),D=br,L=h(s,x,u=="before");return D!=null&&(L.other=h(s,D,u!="before")),L}function Zo(e,t){var n=0;t=Ae(e.doc,t),e.options.lineWrapping||(n=Kr(e.display)*t.ch);var r=ye(e.doc,t.line),i=er(r)+Xn(e.display);return{left:n,right:n,top:i,bottom:i+r.height}}function Ei(e,t,n,r,i){var o=B(e,t,n);return o.xRel=i,r&&(o.outside=r),o}function Oi(e,t,n){var r=e.doc;if(n+=e.display.viewOffset,n<0)return Ei(r.first,0,null,-1,-1);var i=m(r,n),o=r.first+r.size-1;if(i>o)return Ei(r.first+r.size-1,ye(r,o).text.length,null,1,1);t<0&&(t=0);for(var l=ye(r,i);;){var a=Os(e,l,i,t,n),s=as(l,a.ch+(a.xRel>0||a.outside>0?1:0));if(!s)return a;var u=s.find(1);if(u.line==i)return u;l=ye(r,i=u.line)}}function Vo(e,t,n,r){r-=Ni(t);var i=t.text.length,o=Pt(function(l){return Qt(e,n,l-1).bottom<=r},i,0);return i=Pt(function(l){return Qt(e,n,l).top>r},o,i),{begin:o,end:i}}function $o(e,t,n,r){n||(n=qr(e,t));var i=Yn(e,t,Qt(e,n,r),"line").top;return Vo(e,t,n,i)}function Pi(e,t,n,r){return e.bottom<=n?!1:e.top>n?!0:(r?e.left:e.right)>t}function Os(e,t,n,r,i){i-=er(t);var o=qr(e,t),l=Ni(t),a=0,s=t.text.length,u=!0,h=Re(t,e.doc.direction);if(h){var x=(e.options.lineWrapping?Is:Ps)(e,t,n,o,h,r,i);u=x.level!=1,a=u?x.from:x.to-1,s=u?x.to:x.from-1}var D=null,L=null,H=Pt(function(Le){var ke=Qt(e,o,Le);return ke.top+=l,ke.bottom+=l,Pi(ke,r,i,!1)?(ke.top<=i&&ke.left<=r&&(D=Le,L=ke),!0):!1},a,s),Z,ie,ae=!1;if(L){var he=r-L.left=ge.bottom?1:0}return H=Mt(t.text,H,1),Ei(n,H,ie,ae,r-Z)}function Ps(e,t,n,r,i,o,l){var a=Pt(function(x){var D=i[x],L=D.level!=1;return Pi(jt(e,B(n,L?D.to:D.from,L?"before":"after"),"line",t,r),o,l,!0)},0,i.length-1),s=i[a];if(a>0){var u=s.level!=1,h=jt(e,B(n,u?s.from:s.to,u?"after":"before"),"line",t,r);Pi(h,o,l,!0)&&h.top>l&&(s=i[a-1])}return s}function Is(e,t,n,r,i,o,l){var a=Vo(e,t,r,l),s=a.begin,u=a.end;/\s/.test(t.text.charAt(u-1))&&u--;for(var h=null,x=null,D=0;D=u||L.to<=s)){var H=L.level!=1,Z=Qt(e,r,H?Math.min(u,L.to)-1:Math.max(s,L.from)).right,ie=Zie)&&(h=L,x=ie)}}return h||(h=i[i.length-1]),h.fromu&&(h={from:h.from,to:u,level:h.level}),h}var Sr;function jr(e){if(e.cachedTextHeight!=null)return e.cachedTextHeight;if(Sr==null){Sr=c("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)Sr.appendChild(document.createTextNode("x")),Sr.appendChild(c("br"));Sr.appendChild(document.createTextNode("x"))}G(e.measure,Sr);var n=Sr.offsetHeight/50;return n>3&&(e.cachedTextHeight=n),F(e.measure),n||1}function Kr(e){if(e.cachedCharWidth!=null)return e.cachedCharWidth;var t=c("span","xxxxxxxxxx"),n=c("pre",[t],"CodeMirror-line-like");G(e.measure,n);var r=t.getBoundingClientRect(),i=(r.right-r.left)/10;return i>2&&(e.cachedCharWidth=i),i||10}function Ii(e){for(var t=e.display,n={},r={},i=t.gutters.clientLeft,o=t.gutters.firstChild,l=0;o;o=o.nextSibling,++l){var a=e.display.gutterSpecs[l].className;n[a]=o.offsetLeft+o.clientLeft+i,r[a]=o.clientWidth}return{fixedPos:zi(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:n,gutterWidth:r,wrapperWidth:t.wrapper.clientWidth}}function zi(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function el(e){var t=jr(e.display),n=e.options.lineWrapping,r=n&&Math.max(5,e.display.scroller.clientWidth/Kr(e.display)-3);return function(i){if(cr(e.doc,i))return 0;var o=0;if(i.widgets)for(var l=0;l0&&(u=ye(e.doc,s.line).text).length==s.ch){var h=Fe(u,u.length,e.options.tabSize)-u.length;s=B(s.line,Math.max(0,Math.round((o-_o(e.display).left)/Kr(e.display))-h))}return s}function Tr(e,t){if(t>=e.display.viewTo||(t-=e.display.viewFrom,t<0))return null;for(var n=e.display.view,r=0;rt)&&(i.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=i.viewTo)$t&&Li(e.doc,t)i.viewFrom?hr(e):(i.viewFrom+=r,i.viewTo+=r);else if(t<=i.viewFrom&&n>=i.viewTo)hr(e);else if(t<=i.viewFrom){var o=Jn(e,n,n+r,1);o?(i.view=i.view.slice(o.index),i.viewFrom=o.lineN,i.viewTo+=r):hr(e)}else if(n>=i.viewTo){var l=Jn(e,t,t,-1);l?(i.view=i.view.slice(0,l.index),i.viewTo=l.lineN):hr(e)}else{var a=Jn(e,t,t,-1),s=Jn(e,n,n+r,1);a&&s?(i.view=i.view.slice(0,a.index).concat(Gn(e,a.lineN,s.lineN)).concat(i.view.slice(s.index)),i.viewTo+=r):hr(e)}var u=i.externalMeasured;u&&(n=i.lineN&&t=r.viewTo)){var o=r.view[Tr(e,t)];if(o.node!=null){var l=o.changes||(o.changes=[]);ve(l,n)==-1&&l.push(n)}}}function hr(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function Jn(e,t,n,r){var i=Tr(e,t),o,l=e.display.view;if(!$t||n==e.doc.first+e.doc.size)return{index:i,lineN:n};for(var a=e.display.viewFrom,s=0;s0){if(i==l.length-1)return null;o=a+l[i].size-t,i++}else o=a-t;t+=o,n+=o}for(;Li(e.doc,n)!=n;){if(i==(r<0?0:l.length-1))return null;n+=r*l[i-(r<0?1:0)].size,i+=r}return{index:i,lineN:n}}function zs(e,t,n){var r=e.display,i=r.view;i.length==0||t>=r.viewTo||n<=r.viewFrom?(r.view=Gn(e,t,n),r.viewFrom=t):(r.viewFrom>t?r.view=Gn(e,t,r.viewFrom).concat(r.view):r.viewFromn&&(r.view=r.view.slice(0,Tr(e,n)))),r.viewTo=n}function tl(e){for(var t=e.display.view,n=0,r=0;r=e.display.viewTo||s.to().line0?l:e.defaultCharWidth())+"px"}if(r.other){var a=n.appendChild(c("div"," ","CodeMirror-cursor CodeMirror-secondarycursor"));a.style.display="",a.style.left=r.other.left+"px",a.style.top=r.other.top+"px",a.style.height=(r.other.bottom-r.other.top)*.85+"px"}}function Zn(e,t){return e.top-t.top||e.left-t.left}function Bs(e,t,n){var r=e.display,i=e.doc,o=document.createDocumentFragment(),l=_o(e.display),a=l.left,s=Math.max(r.sizerWidth,wr(e)-r.sizer.offsetLeft)-l.right,u=i.direction=="ltr";function h(se,ge,Le,ke){ge<0&&(ge=0),ge=Math.round(ge),ke=Math.round(ke),o.appendChild(c("div",null,"CodeMirror-selected","position: absolute; left: "+se+`px; - top: `+ge+"px; width: "+(Le??s-se)+`px; - height: `+(ke-ge)+"px"))}function x(se,ge,Le){var ke=ye(i,se),Ee=ke.text.length,Ke,st;function Xe(tt,Ct){return Qn(e,B(se,tt),"div",ke,Ct)}function Nt(tt,Ct,ft){var nt=$o(e,ke,null,tt),rt=Ct=="ltr"==(ft=="after")?"left":"right",Ze=ft=="after"?nt.begin:nt.end-(/\s/.test(ke.text.charAt(nt.end-1))?2:1);return Xe(Ze,rt)[rt]}var Tt=Re(ke,i.direction);return or(Tt,ge||0,Le??Ee,function(tt,Ct,ft,nt){var rt=ft=="ltr",Ze=Xe(tt,rt?"left":"right"),Dt=Xe(Ct-1,rt?"right":"left"),nn=ge==null&&tt==0,yr=Le==null&&Ct==Ee,vt=nt==0,Jt=!Tt||nt==Tt.length-1;if(Dt.top-Ze.top<=3){var ut=(u?nn:yr)&&vt,co=(u?yr:nn)&&Jt,ir=ut?a:(rt?Ze:Dt).left,Ar=co?s:(rt?Dt:Ze).right;h(ir,Ze.top,Ar-ir,Ze.bottom)}else{var Nr,bt,on,ho;rt?(Nr=u&&nn&&vt?a:Ze.left,bt=u?s:Nt(tt,ft,"before"),on=u?a:Nt(Ct,ft,"after"),ho=u&&yr&&Jt?s:Dt.right):(Nr=u?Nt(tt,ft,"before"):a,bt=!u&&nn&&vt?s:Ze.right,on=!u&&yr&&Jt?a:Dt.left,ho=u?Nt(Ct,ft,"after"):s),h(Nr,Ze.top,bt-Nr,Ze.bottom),Ze.bottom0?t.blinker=setInterval(function(){e.hasFocus()||Ur(e),t.cursorDiv.style.visibility=(n=!n)?"":"hidden"},e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function nl(e){e.hasFocus()||(e.display.input.focus(),e.state.focused||_i(e))}function Hi(e){e.state.delayingBlurEvent=!0,setTimeout(function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,e.state.focused&&Ur(e))},100)}function _i(e,t){e.state.delayingBlurEvent&&!e.state.draggingText&&(e.state.delayingBlurEvent=!1),e.options.readOnly!="nocursor"&&(e.state.focused||(Ye(e,"focus",e,t),e.state.focused=!0,j(e.display.wrapper,"CodeMirror-focused"),!e.curOp&&e.display.selForContextMenu!=e.doc.sel&&(e.display.input.reset(),Y&&setTimeout(function(){return e.display.input.reset(!0)},20)),e.display.input.receivedFocus()),Wi(e))}function Ur(e,t){e.state.delayingBlurEvent||(e.state.focused&&(Ye(e,"blur",e,t),e.state.focused=!1,$(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout(function(){e.state.focused||(e.display.shift=!1)},150))}function Vn(e){for(var t=e.display,n=t.lineDiv.offsetTop,r=Math.max(0,t.scroller.getBoundingClientRect().top),i=t.lineDiv.getBoundingClientRect().top,o=0,l=0;l.005||L<-.005)&&(ie.display.sizerWidth){var Z=Math.ceil(h/Kr(e.display));Z>e.display.maxLineLength&&(e.display.maxLineLength=Z,e.display.maxLine=a.line,e.display.maxLineChanged=!0)}}}Math.abs(o)>2&&(t.scroller.scrollTop+=o)}function il(e){if(e.widgets)for(var t=0;t=l&&(o=m(t,er(ye(t,s))-e.wrapper.clientHeight),l=s)}return{from:o,to:Math.max(l,o+1)}}function Rs(e,t){if(!Qe(e,"scrollCursorIntoView")){var n=e.display,r=n.sizer.getBoundingClientRect(),i=null,o=n.wrapper.ownerDocument;if(t.top+r.top<0?i=!0:t.bottom+r.top>(o.defaultView.innerHeight||o.documentElement.clientHeight)&&(i=!1),i!=null&&!O){var l=c("div","​",null,`position: absolute; - top: `+(t.top-n.viewOffset-Xn(e.display))+`px; - height: `+(t.bottom-t.top+Yt(e)+n.barHeight)+`px; - left: `+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(l),l.scrollIntoView(i),e.display.lineSpace.removeChild(l)}}}function Ws(e,t,n,r){r==null&&(r=0);var i;!e.options.lineWrapping&&t==n&&(n=t.sticky=="before"?B(t.line,t.ch+1,"before"):t,t=t.ch?B(t.line,t.sticky=="before"?t.ch-1:t.ch,"after"):t);for(var o=0;o<5;o++){var l=!1,a=jt(e,t),s=!n||n==t?a:jt(e,n);i={left:Math.min(a.left,s.left),top:Math.min(a.top,s.top)-r,right:Math.max(a.left,s.left),bottom:Math.max(a.bottom,s.bottom)+r};var u=qi(e,i),h=e.doc.scrollTop,x=e.doc.scrollLeft;if(u.scrollTop!=null&&(xn(e,u.scrollTop),Math.abs(e.doc.scrollTop-h)>1&&(l=!0)),u.scrollLeft!=null&&(Cr(e,u.scrollLeft),Math.abs(e.doc.scrollLeft-x)>1&&(l=!0)),!l)break}return i}function Hs(e,t){var n=qi(e,t);n.scrollTop!=null&&xn(e,n.scrollTop),n.scrollLeft!=null&&Cr(e,n.scrollLeft)}function qi(e,t){var n=e.display,r=jr(e.display);t.top<0&&(t.top=0);var i=e.curOp&&e.curOp.scrollTop!=null?e.curOp.scrollTop:n.scroller.scrollTop,o=Fi(e),l={};t.bottom-t.top>o&&(t.bottom=t.top+o);var a=e.doc.height+Mi(n),s=t.topa-r;if(t.topi+o){var h=Math.min(t.top,(u?a:t.bottom)-o);h!=i&&(l.scrollTop=h)}var x=e.options.fixedGutter?0:n.gutters.offsetWidth,D=e.curOp&&e.curOp.scrollLeft!=null?e.curOp.scrollLeft:n.scroller.scrollLeft-x,L=wr(e)-n.gutters.offsetWidth,H=t.right-t.left>L;return H&&(t.right=t.left+L),t.left<10?l.scrollLeft=0:t.leftL+D-3&&(l.scrollLeft=t.right+(H?0:10)-L),l}function ji(e,t){t!=null&&(ei(e),e.curOp.scrollTop=(e.curOp.scrollTop==null?e.doc.scrollTop:e.curOp.scrollTop)+t)}function Gr(e){ei(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function mn(e,t,n){(t!=null||n!=null)&&ei(e),t!=null&&(e.curOp.scrollLeft=t),n!=null&&(e.curOp.scrollTop=n)}function _s(e,t){ei(e),e.curOp.scrollToPos=t}function ei(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var n=Zo(e,t.from),r=Zo(e,t.to);ol(e,n,r,t.margin)}}function ol(e,t,n,r){var i=qi(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});mn(e,i.scrollLeft,i.scrollTop)}function xn(e,t){Math.abs(e.doc.scrollTop-t)<2||(_||Ui(e,{top:t}),ll(e,t,!0),_&&Ui(e),kn(e,100))}function ll(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),!(e.display.scroller.scrollTop==t&&!n)&&(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function Cr(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),!((n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r)&&(e.doc.scrollLeft=t,cl(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function yn(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+Mi(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+Yt(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}var Dr=function(e,t,n){this.cm=n;var r=this.vert=c("div",[c("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=c("div",[c("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=i.tabIndex=-1,e(r),e(i),Se(r,"scroll",function(){r.clientHeight&&t(r.scrollTop,"vertical")}),Se(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,k&&I<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")};Dr.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1,n=e.scrollHeight>e.clientHeight+1,r=e.nativeBarWidth;if(n){this.vert.style.display="block",this.vert.style.bottom=t?r+"px":"0";var i=e.viewHeight-(t?r:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+i)+"px"}else this.vert.scrollTop=0,this.vert.style.display="",this.vert.firstChild.style.height="0";if(t){this.horiz.style.display="block",this.horiz.style.right=n?r+"px":"0",this.horiz.style.left=e.barLeft+"px";var o=e.viewWidth-e.barLeft-(n?r:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+o)+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedZeroWidth&&e.clientHeight>0&&(r==0&&this.zeroWidthHack(),this.checkedZeroWidth=!0),{right:n?r:0,bottom:t?r:0}},Dr.prototype.setScrollLeft=function(e){this.horiz.scrollLeft!=e&&(this.horiz.scrollLeft=e),this.disableHoriz&&this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")},Dr.prototype.setScrollTop=function(e){this.vert.scrollTop!=e&&(this.vert.scrollTop=e),this.disableVert&&this.enableZeroWidthBar(this.vert,this.disableVert,"vert")},Dr.prototype.zeroWidthHack=function(){var e=z&&!ue?"12px":"18px";this.horiz.style.height=this.vert.style.width=e,this.horiz.style.visibility=this.vert.style.visibility="hidden",this.disableHoriz=new Ce,this.disableVert=new Ce},Dr.prototype.enableZeroWidthBar=function(e,t,n){e.style.visibility="";function r(){var i=e.getBoundingClientRect(),o=n=="vert"?document.elementFromPoint(i.right-1,(i.top+i.bottom)/2):document.elementFromPoint((i.right+i.left)/2,i.bottom-1);o!=e?e.style.visibility="hidden":t.set(1e3,r)}t.set(1e3,r)},Dr.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz),e.removeChild(this.vert)};var bn=function(){};bn.prototype.update=function(){return{bottom:0,right:0}},bn.prototype.setScrollLeft=function(){},bn.prototype.setScrollTop=function(){},bn.prototype.clear=function(){};function Xr(e,t){t||(t=yn(e));var n=e.display.barWidth,r=e.display.barHeight;al(e,t);for(var i=0;i<4&&n!=e.display.barWidth||r!=e.display.barHeight;i++)n!=e.display.barWidth&&e.options.lineWrapping&&Vn(e),al(e,yn(e)),n=e.display.barWidth,r=e.display.barHeight}function al(e,t){var n=e.display,r=n.scrollbars.update(t);n.sizer.style.paddingRight=(n.barWidth=r.right)+"px",n.sizer.style.paddingBottom=(n.barHeight=r.bottom)+"px",n.heightForcer.style.borderBottom=r.bottom+"px solid transparent",r.right&&r.bottom?(n.scrollbarFiller.style.display="block",n.scrollbarFiller.style.height=r.bottom+"px",n.scrollbarFiller.style.width=r.right+"px"):n.scrollbarFiller.style.display="",r.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(n.gutterFiller.style.display="block",n.gutterFiller.style.height=r.bottom+"px",n.gutterFiller.style.width=t.gutterWidth+"px"):n.gutterFiller.style.display=""}var sl={native:Dr,null:bn};function ul(e){e.display.scrollbars&&(e.display.scrollbars.clear(),e.display.scrollbars.addClass&&$(e.display.wrapper,e.display.scrollbars.addClass)),e.display.scrollbars=new sl[e.options.scrollbarStyle](function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller),Se(t,"mousedown",function(){e.state.focused&&setTimeout(function(){return e.display.input.focus()},0)}),t.setAttribute("cm-not-content","true")},function(t,n){n=="horizontal"?Cr(e,t):xn(e,t)},e),e.display.scrollbars.addClass&&j(e.display.wrapper,e.display.scrollbars.addClass)}var qs=0;function Mr(e){e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:0,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++qs,markArrays:null},ys(e.curOp)}function Fr(e){var t=e.curOp;t&&ks(t,function(n){for(var r=0;r=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new ti(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Us(e){e.updatedDisplay=e.mustUpdate&&Ki(e.cm,e.update)}function Gs(e){var t=e.cm,n=t.display;e.updatedDisplay&&Vn(t),e.barMeasure=yn(t),n.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=jo(t,n.maxLine,n.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(n.scroller.clientWidth,n.sizer.offsetLeft+e.adjustWidthTo+Yt(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,n.sizer.offsetLeft+e.adjustWidthTo-wr(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=n.input.prepareSelection())}function Xs(e){var t=e.cm;e.adjustWidthTo!=null&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLeft=e.display.viewTo)){var n=+new Date+e.options.workTime,r=fn(e,t.highlightFrontier),i=[];t.iter(r.line,Math.min(t.first+t.size,e.display.viewTo+500),function(o){if(r.line>=e.display.viewFrom){var l=o.styles,a=o.text.length>e.options.maxHighlightLength?Gt(t.mode,r.state):null,s=mo(e,o,r,!0);a&&(r.state=a),o.styles=s.styles;var u=o.styleClasses,h=s.classes;h?o.styleClasses=h:u&&(o.styleClasses=null);for(var x=!l||l.length!=o.styles.length||u!=h&&(!u||!h||u.bgClass!=h.bgClass||u.textClass!=h.textClass),D=0;!x&&Dn)return kn(e,e.options.workDelay),!0}),t.highlightFrontier=r.line,t.modeFrontier=Math.max(t.modeFrontier,r.line),i.length&&At(e,function(){for(var o=0;o=n.viewFrom&&t.visible.to<=n.viewTo&&(n.updateLineNumbers==null||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&tl(e)==0)return!1;dl(e)&&(hr(e),t.dims=Ii(e));var i=r.first+r.size,o=Math.max(t.visible.from-e.options.viewportMargin,r.first),l=Math.min(i,t.visible.to+e.options.viewportMargin);n.viewFroml&&n.viewTo-l<20&&(l=Math.min(i,n.viewTo)),$t&&(o=Li(e.doc,o),l=No(e.doc,l));var a=o!=n.viewFrom||l!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth;zs(e,o,l),n.viewOffset=er(ye(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px";var s=tl(e);if(!a&&s==0&&!t.force&&n.renderedView==n.view&&(n.updateLineNumbers==null||n.updateLineNumbers>=n.viewTo))return!1;var u=Zs(e);return s>4&&(n.lineDiv.style.display="none"),$s(e,n.updateLineNumbers,t.dims),s>4&&(n.lineDiv.style.display=""),n.renderedView=n.view,Vs(u),F(n.cursorDiv),F(n.selectionDiv),n.gutters.style.height=n.sizer.style.minHeight=0,a&&(n.lastWrapHeight=t.wrapperHeight,n.lastWrapWidth=t.wrapperWidth,kn(e,400)),n.updateLineNumbers=null,!0}function fl(e,t){for(var n=t.viewport,r=!0;;r=!1){if(!r||!e.options.lineWrapping||t.oldDisplayWidth==wr(e)){if(n&&n.top!=null&&(n={top:Math.min(e.doc.height+Mi(e.display)-Fi(e),n.top)}),t.visible=$n(e.display,e.doc,n),t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break}else r&&(t.visible=$n(e.display,e.doc,n));if(!Ki(e,t))break;Vn(e);var i=yn(e);vn(e),Xr(e,i),Xi(e,i),t.force=!1}t.signal(e,"update",e),(e.display.viewFrom!=e.display.reportedViewFrom||e.display.viewTo!=e.display.reportedViewTo)&&(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function Ui(e,t){var n=new ti(e,t);if(Ki(e,n)){Vn(e),fl(e,n);var r=yn(e);vn(e),Xr(e,r),Xi(e,r),n.finish()}}function $s(e,t,n){var r=e.display,i=e.options.lineNumbers,o=r.lineDiv,l=o.firstChild;function a(H){var Z=H.nextSibling;return Y&&z&&e.display.currentWheelTarget==H?H.style.display="none":H.parentNode.removeChild(H),Z}for(var s=r.view,u=r.viewFrom,h=0;h-1&&(L=!1),zo(e,x,u,n)),L&&(F(x.lineNumber),x.lineNumber.appendChild(document.createTextNode(re(e.options,u)))),l=x.node.nextSibling}u+=x.size}for(;l;)l=a(l)}function Gi(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px",ot(e,"gutterChanged",e)}function Xi(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+Yt(e)+"px"}function cl(e){var t=e.display,n=t.view;if(!(!t.alignWidgets&&(!t.gutters.firstChild||!e.options.fixedGutter))){for(var r=zi(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=r+"px",l=0;l=105&&(i.wrapper.style.clipPath="inset(0px)"),i.wrapper.setAttribute("translate","no"),k&&I<8&&(i.gutters.style.zIndex=-1,i.scroller.style.paddingRight=0),!Y&&!(_&&N)&&(i.scroller.draggable=!0),e&&(e.appendChild?e.appendChild(i.wrapper):e(i.wrapper)),i.viewFrom=i.viewTo=t.first,i.reportedViewFrom=i.reportedViewTo=t.first,i.view=[],i.renderedView=null,i.externalMeasured=null,i.viewOffset=0,i.lastWrapHeight=i.lastWrapWidth=0,i.updateLineNumbers=null,i.nativeBarWidth=i.barHeight=i.barWidth=0,i.scrollbarsClipped=!1,i.lineNumWidth=i.lineNumInnerWidth=i.lineNumChars=null,i.alignWidgets=!1,i.cachedCharWidth=i.cachedTextHeight=i.cachedPaddingH=null,i.maxLine=null,i.maxLineLength=0,i.maxLineChanged=!1,i.wheelDX=i.wheelDY=i.wheelStartX=i.wheelStartY=null,i.shift=!1,i.selForContextMenu=null,i.activeTouch=null,i.gutterSpecs=Yi(r.gutters,r.lineNumbers),hl(i),n.init(i)}var ri=0,rr=null;k?rr=-.53:_?rr=15:S?rr=-.7:V&&(rr=-1/3);function pl(e){var t=e.wheelDeltaX,n=e.wheelDeltaY;return t==null&&e.detail&&e.axis==e.HORIZONTAL_AXIS&&(t=e.detail),n==null&&e.detail&&e.axis==e.VERTICAL_AXIS?n=e.detail:n==null&&(n=e.wheelDelta),{x:t,y:n}}function tu(e){var t=pl(e);return t.x*=rr,t.y*=rr,t}function gl(e,t){S&&R==102&&(e.display.chromeScrollHack==null?e.display.sizer.style.pointerEvents="none":clearTimeout(e.display.chromeScrollHack),e.display.chromeScrollHack=setTimeout(function(){e.display.chromeScrollHack=null,e.display.sizer.style.pointerEvents=""},100));var n=pl(t),r=n.x,i=n.y,o=rr;t.deltaMode===0&&(r=t.deltaX,i=t.deltaY,o=1);var l=e.display,a=l.scroller,s=a.scrollWidth>a.clientWidth,u=a.scrollHeight>a.clientHeight;if(r&&s||i&&u){if(i&&z&&Y){e:for(var h=t.target,x=l.view;h!=a;h=h.parentNode)for(var D=0;D=0&&ce(e,r.to())<=0)return n}return-1};var He=function(e,t){this.anchor=e,this.head=t};He.prototype.from=function(){return Wr(this.anchor,this.head)},He.prototype.to=function(){return wt(this.anchor,this.head)},He.prototype.empty=function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch};function Kt(e,t,n){var r=e&&e.options.selectionsMayTouch,i=t[n];t.sort(function(D,L){return ce(D.from(),L.from())}),n=ve(t,i);for(var o=1;o0:s>=0){var u=Wr(a.from(),l.from()),h=wt(a.to(),l.to()),x=a.empty()?l.from()==l.head:a.from()==a.head;o<=n&&--n,t.splice(--o,2,new He(x?h:u,x?u:h))}}return new Ot(t,n)}function pr(e,t){return new Ot([new He(e,t||e)],0)}function gr(e){return e.text?B(e.from.line+e.text.length-1,we(e.text).length+(e.text.length==1?e.from.ch:0)):e.to}function vl(e,t){if(ce(e,t.from)<0)return e;if(ce(e,t.to)<=0)return gr(t);var n=e.line+t.text.length-(t.to.line-t.from.line)-1,r=e.ch;return e.line==t.to.line&&(r+=gr(t).ch-t.to.ch),B(n,r)}function Qi(e,t){for(var n=[],r=0;r1&&e.remove(a.line+1,H-1),e.insert(a.line+1,ae)}ot(e,"change",e,t)}function vr(e,t,n){function r(i,o,l){if(i.linked)for(var a=0;a1&&!e.done[e.done.length-2].ranges)return e.done.pop(),we(e.done)}function wl(e,t,n,r){var i=e.history;i.undone.length=0;var o=+new Date,l,a;if((i.lastOp==r||i.lastOrigin==t.origin&&t.origin&&(t.origin.charAt(0)=="+"&&i.lastModTime>o-(e.cm?e.cm.options.historyEventDelay:500)||t.origin.charAt(0)=="*"))&&(l=iu(i,i.lastOp==r)))a=we(l.changes),ce(t.from,t.to)==0&&ce(t.from,a.to)==0?a.to=gr(t):l.changes.push(Vi(e,t));else{var s=we(i.done);for((!s||!s.ranges)&&ii(e.sel,i.done),l={changes:[Vi(e,t)],generation:i.generation},i.done.push(l);i.done.length>i.undoDepth;)i.done.shift(),i.done[0].ranges||i.done.shift()}i.done.push(n),i.generation=++i.maxGeneration,i.lastModTime=i.lastSelTime=o,i.lastOp=i.lastSelOp=r,i.lastOrigin=i.lastSelOrigin=t.origin,a||Ye(e,"historyAdded")}function ou(e,t,n,r){var i=t.charAt(0);return i=="*"||i=="+"&&n.ranges.length==r.ranges.length&&n.somethingSelected()==r.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function lu(e,t,n,r){var i=e.history,o=r&&r.origin;n==i.lastSelOp||o&&i.lastSelOrigin==o&&(i.lastModTime==i.lastSelTime&&i.lastOrigin==o||ou(e,o,we(i.done),t))?i.done[i.done.length-1]=t:ii(t,i.done),i.lastSelTime=+new Date,i.lastSelOrigin=o,i.lastSelOp=n,r&&r.clearRedo!==!1&&kl(i.undone)}function ii(e,t){var n=we(t);n&&n.ranges&&n.equals(e)||t.push(e)}function Sl(e,t,n,r){var i=t["spans_"+e.id],o=0;e.iter(Math.max(e.first,n),Math.min(e.first+e.size,r),function(l){l.markedSpans&&((i||(i=t["spans_"+e.id]={}))[o]=l.markedSpans),++o})}function au(e){if(!e)return null;for(var t,n=0;n-1&&(we(a)[x]=u[x],delete u[x])}}return r}function $i(e,t,n,r){if(r){var i=e.anchor;if(n){var o=ce(t,i)<0;o!=ce(n,i)<0?(i=t,t=n):o!=ce(t,n)<0&&(t=n)}return new He(i,t)}else return new He(n||t,t)}function oi(e,t,n,r,i){i==null&&(i=e.cm&&(e.cm.display.shift||e.extend)),gt(e,new Ot([$i(e.sel.primary(),t,n,i)],0),r)}function Tl(e,t,n){for(var r=[],i=e.cm&&(e.cm.display.shift||e.extend),o=0;o=t.ch:a.to>t.ch))){if(i&&(Ye(s,"beforeCursorEnter"),s.explicitlyCleared))if(o.markedSpans){--l;continue}else break;if(!s.atomic)continue;if(n){var x=s.find(r<0?1:-1),D=void 0;if((r<0?h:u)&&(x=Nl(e,x,-r,x&&x.line==t.line?o:null)),x&&x.line==t.line&&(D=ce(x,n))&&(r<0?D<0:D>0))return Qr(e,x,t,r,i)}var L=s.find(r<0?-1:1);return(r<0?u:h)&&(L=Nl(e,L,r,L.line==t.line?o:null)),L?Qr(e,L,t,r,i):null}}return t}function ai(e,t,n,r,i){var o=r||1,l=Qr(e,t,n,o,i)||!i&&Qr(e,t,n,o,!0)||Qr(e,t,n,-o,i)||!i&&Qr(e,t,n,-o,!0);return l||(e.cantEdit=!0,B(e.first,0))}function Nl(e,t,n,r){return n<0&&t.ch==0?t.line>e.first?Ae(e,B(t.line-1)):null:n>0&&t.ch==(r||ye(e,t.line)).text.length?t.line=0;--i)Pl(e,{from:r[i].from,to:r[i].to,text:i?[""]:t.text,origin:t.origin});else Pl(e,t)}}function Pl(e,t){if(!(t.text.length==1&&t.text[0]==""&&ce(t.from,t.to)==0)){var n=Qi(e,t);wl(e,t,n,e.cm?e.cm.curOp.id:NaN),Ln(e,t,n,wi(e,t));var r=[];vr(e,function(i,o){!o&&ve(r,i.history)==-1&&(Rl(i.history,t),r.push(i.history)),Ln(i,t,null,wi(i,t))})}}function si(e,t,n){var r=e.cm&&e.cm.state.suppressEdits;if(!(r&&!n)){for(var i=e.history,o,l=e.sel,a=t=="undo"?i.done:i.undone,s=t=="undo"?i.undone:i.done,u=0;u=0;--L){var H=D(L);if(H)return H.v}}}}function Il(e,t){if(t!=0&&(e.first+=t,e.sel=new Ot(Ie(e.sel.ranges,function(i){return new He(B(i.anchor.line+t,i.anchor.ch),B(i.head.line+t,i.head.ch))}),e.sel.primIndex),e.cm)){St(e.cm,e.first,e.first-t,t);for(var n=e.cm.display,r=n.viewFrom;re.lastLine())){if(t.from.lineo&&(t={from:t.from,to:B(o,ye(e,o).text.length),text:[t.text[0]],origin:t.origin}),t.removed=Vt(e,t.from,t.to),n||(n=Qi(e,t)),e.cm?fu(e.cm,t,r):Zi(e,t,r),li(e,n,Ve),e.cantEdit&&ai(e,B(e.firstLine(),0))&&(e.cantEdit=!1)}}function fu(e,t,n){var r=e.doc,i=e.display,o=t.from,l=t.to,a=!1,s=o.line;e.options.lineWrapping||(s=f(qt(ye(r,o.line))),r.iter(s,l.line+1,function(L){if(L==i.maxLine)return a=!0,!0})),r.sel.contains(t.from,t.to)>-1&&It(e),Zi(r,t,n,el(e)),e.options.lineWrapping||(r.iter(s,o.line+t.text.length,function(L){var H=Un(L);H>i.maxLineLength&&(i.maxLine=L,i.maxLineLength=H,i.maxLineChanged=!0,a=!1)}),a&&(e.curOp.updateMaxLine=!0)),$a(r,o.line),kn(e,400);var u=t.text.length-(l.line-o.line)-1;t.full?St(e):o.line==l.line&&t.text.length==1&&!xl(e.doc,t)?dr(e,o.line,"text"):St(e,o.line,l.line+1,u);var h=Ft(e,"changes"),x=Ft(e,"change");if(x||h){var D={from:o,to:l,text:t.text,removed:t.removed,origin:t.origin};x&&ot(e,"change",e,D),h&&(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(D)}e.display.selForContextMenu=null}function Zr(e,t,n,r,i){var o;r||(r=n),ce(r,n)<0&&(o=[r,n],n=o[0],r=o[1]),typeof t=="string"&&(t=e.splitLines(t)),Jr(e,{from:n,to:r,text:t,origin:i})}function zl(e,t,n,r){n1||!(this.children[0]instanceof Cn))){var a=[];this.collapse(a),this.children=[new Cn(a)],this.children[0].parent=this}},collapse:function(e){for(var t=0;t50){for(var l=i.lines.length%25+25,a=l;a10);e.parent.maybeSpill()}},iterN:function(e,t,n){for(var r=0;re.display.maxLineLength&&(e.display.maxLine=u,e.display.maxLineLength=h,e.display.maxLineChanged=!0)}r!=null&&e&&this.collapsed&&St(e,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,e&&Fl(e.doc)),e&&ot(e,"markerCleared",e,this,r,i),t&&Fr(e),this.parent&&this.parent.clear()}},mr.prototype.find=function(e,t){e==null&&this.type=="bookmark"&&(e=1);for(var n,r,i=0;i0||l==0&&o.clearWhenEmpty!==!1)return o;if(o.replacedWith&&(o.collapsed=!0,o.widgetNode=T("span",[o.replacedWith],"CodeMirror-widget"),r.handleMouseEvents||o.widgetNode.setAttribute("cm-ignore-events","true"),r.insertLeft&&(o.widgetNode.insertLeft=!0)),o.collapsed){if(Ao(e,t.line,t,n,o)||t.line!=n.line&&Ao(e,n.line,t,n,o))throw new Error("Inserting collapsed marker partially overlapping an existing one");ts()}o.addToHistory&&wl(e,{from:t,to:n,origin:"markText"},e.sel,NaN);var a=t.line,s=e.cm,u;if(e.iter(a,n.line+1,function(x){s&&o.collapsed&&!s.options.lineWrapping&&qt(x)==s.display.maxLine&&(u=!0),o.collapsed&&a!=t.line&&Et(x,0),ns(x,new _n(o,a==t.line?t.ch:null,a==n.line?n.ch:null),e.cm&&e.cm.curOp),++a}),o.collapsed&&e.iter(t.line,n.line+1,function(x){cr(e,x)&&Et(x,0)}),o.clearOnEnter&&Se(o,"beforeCursorEnter",function(){return o.clear()}),o.readOnly&&(es(),(e.history.done.length||e.history.undone.length)&&e.clearHistory()),o.collapsed&&(o.id=++Hl,o.atomic=!0),s){if(u&&(s.curOp.updateMaxLine=!0),o.collapsed)St(s,t.line,n.line+1);else if(o.className||o.startStyle||o.endStyle||o.css||o.attributes||o.title)for(var h=t.line;h<=n.line;h++)dr(s,h,"text");o.atomic&&Fl(s.doc),ot(s,"markerAdded",s,o)}return o}var Fn=function(e,t){this.markers=e,this.primary=t;for(var n=0;n=0;s--)Jr(this,r[s]);a?Dl(this,a):this.cm&&Gr(this.cm)}),undo:at(function(){si(this,"undo")}),redo:at(function(){si(this,"redo")}),undoSelection:at(function(){si(this,"undo",!0)}),redoSelection:at(function(){si(this,"redo",!0)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){for(var e=this.history,t=0,n=0,r=0;r=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(e,t,n){e=Ae(this,e),t=Ae(this,t);var r=[],i=e.line;return this.iter(e.line,t.line+1,function(o){var l=o.markedSpans;if(l)for(var a=0;a=s.to||s.from==null&&i!=e.line||s.from!=null&&i==t.line&&s.from>=t.ch)&&(!n||n(s.marker))&&r.push(s.marker.parent||s.marker)}++i}),r},getAllMarks:function(){var e=[];return this.iter(function(t){var n=t.markedSpans;if(n)for(var r=0;re)return t=e,!0;e-=o,++n}),Ae(this,B(n,t))},indexFromPos:function(e){e=Ae(this,e);var t=e.ch;if(e.linet&&(t=e.from),e.to!=null&&e.to-1){t.state.draggingText(e),setTimeout(function(){return t.display.input.focus()},20);return}try{var h=e.dataTransfer.getData("Text");if(h){var x;if(t.state.draggingText&&!t.state.draggingText.copy&&(x=t.listSelections()),li(t.doc,pr(n,n)),x)for(var D=0;D=0;a--)Zr(e.doc,"",r[a].from,r[a].to,"+delete");Gr(e)})}function to(e,t,n){var r=Mt(e.text,t+n,n);return r<0||r>e.text.length?null:r}function ro(e,t,n){var r=to(e,t.ch,n);return r==null?null:new B(t.line,r,n<0?"after":"before")}function no(e,t,n,r,i){if(e){t.doc.direction=="rtl"&&(i=-i);var o=Re(n,t.doc.direction);if(o){var l=i<0?we(o):o[0],a=i<0==(l.level==1),s=a?"after":"before",u;if(l.level>0||t.doc.direction=="rtl"){var h=qr(t,n);u=i<0?n.text.length-1:0;var x=Qt(t,h,u).top;u=Pt(function(D){return Qt(t,h,D).top==x},i<0==(l.level==1)?l.from:l.to-1,u),s=="before"&&(u=to(n,u,1))}else u=i<0?l.to:l.from;return new B(r,u,s)}}return new B(r,i<0?n.text.length:0,i<0?"before":"after")}function Lu(e,t,n,r){var i=Re(t,e.doc.direction);if(!i)return ro(t,n,r);n.ch>=t.text.length?(n.ch=t.text.length,n.sticky="before"):n.ch<=0&&(n.ch=0,n.sticky="after");var o=lr(i,n.ch,n.sticky),l=i[o];if(e.doc.direction=="ltr"&&l.level%2==0&&(r>0?l.to>n.ch:l.from=l.from&&D>=h.begin)){var L=x?"before":"after";return new B(n.line,D,L)}}var H=function(ae,he,se){for(var ge=function(Ke,st){return st?new B(n.line,a(Ke,1),"before"):new B(n.line,Ke,"after")};ae>=0&&ae0==(Le.level!=1),Ee=ke?se.begin:a(se.end,-1);if(Le.from<=Ee&&Ee0?h.end:a(h.begin,-1);return ie!=null&&!(r>0&&ie==t.text.length)&&(Z=H(r>0?0:i.length-1,r,u(ie)),Z)?Z:null}var En={selectAll:El,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),Ve)},killLine:function(e){return en(e,function(t){if(t.empty()){var n=ye(e.doc,t.head.line).text.length;return t.head.ch==n&&t.head.line0)i=new B(i.line,i.ch+1),e.replaceRange(o.charAt(i.ch-1)+o.charAt(i.ch-2),B(i.line,i.ch-2),i,"+transpose");else if(i.line>e.doc.first){var l=ye(e.doc,i.line-1).text;l&&(i=new B(i.line,1),e.replaceRange(o.charAt(0)+e.doc.lineSeparator()+l.charAt(l.length-1),B(i.line-1,l.length-1),i,"+transpose"))}}n.push(new He(i,i))}e.setSelections(n)})},newlineAndIndent:function(e){return At(e,function(){for(var t=e.listSelections(),n=t.length-1;n>=0;n--)e.replaceRange(e.doc.lineSeparator(),t[n].anchor,t[n].head,"+input");t=e.listSelections();for(var r=0;re&&ce(t,this.pos)==0&&n==this.button};var Pn,In;function Nu(e,t){var n=+new Date;return In&&In.compare(n,e,t)?(Pn=In=null,"triple"):Pn&&Pn.compare(n,e,t)?(In=new oo(n,e,t),Pn=null,"double"):(Pn=new oo(n,e,t),In=null,"single")}function ra(e){var t=this,n=t.display;if(!(Qe(t,e)||n.activeTouch&&n.input.supportsTouch())){if(n.input.ensurePolled(),n.shift=e.shiftKey,tr(n,e)){Y||(n.scroller.draggable=!1,setTimeout(function(){return n.scroller.draggable=!0},100));return}if(!lo(t,e)){var r=Lr(t,e),i=Rt(e),o=r?Nu(r,i):"single";le(t).focus(),i==1&&t.state.selectingText&&t.state.selectingText(e),!(r&&Eu(t,i,r,o,e))&&(i==1?r?Pu(t,r,o,e):ln(e)==n.scroller&&pt(e):i==2?(r&&oi(t.doc,r),setTimeout(function(){return n.input.focus()},20)):i==3&&(J?t.display.input.onContextMenu(e):Hi(t)))}}}function Eu(e,t,n,r,i){var o="Click";return r=="double"?o="Double"+o:r=="triple"&&(o="Triple"+o),o=(t==1?"Left":t==2?"Middle":"Right")+o,On(e,Xl(o,i),i,function(l){if(typeof l=="string"&&(l=En[l]),!l)return!1;var a=!1;try{e.isReadOnly()&&(e.state.suppressEdits=!0),a=l(e,n)!=qe}finally{e.state.suppressEdits=!1}return a})}function Ou(e,t,n){var r=e.getOption("configureMouse"),i=r?r(e,t,n):{};if(i.unit==null){var o=X?n.shiftKey&&n.metaKey:n.altKey;i.unit=o?"rectangle":t=="single"?"char":t=="double"?"word":"line"}return(i.extend==null||e.doc.extend)&&(i.extend=e.doc.extend||n.shiftKey),i.addNew==null&&(i.addNew=z?n.metaKey:n.ctrlKey),i.moveOnDrag==null&&(i.moveOnDrag=!(z?n.altKey:n.ctrlKey)),i}function Pu(e,t,n,r){k?setTimeout(xe(nl,e),0):e.curOp.focus=y(fe(e));var i=Ou(e,n,r),o=e.doc.sel,l;e.options.dragDrop&&xi&&!e.isReadOnly()&&n=="single"&&(l=o.contains(t))>-1&&(ce((l=o.ranges[l]).from(),t)<0||t.xRel>0)&&(ce(l.to(),t)>0||t.xRel<0)?Iu(e,r,t,i):zu(e,r,t,i)}function Iu(e,t,n,r){var i=e.display,o=!1,l=lt(e,function(u){Y&&(i.scroller.draggable=!1),e.state.draggingText=!1,e.state.delayingBlurEvent&&(e.hasFocus()?e.state.delayingBlurEvent=!1:Hi(e)),ht(i.wrapper.ownerDocument,"mouseup",l),ht(i.wrapper.ownerDocument,"mousemove",a),ht(i.scroller,"dragstart",s),ht(i.scroller,"drop",l),o||(pt(u),r.addNew||oi(e.doc,n,null,null,r.extend),Y&&!V||k&&I==9?setTimeout(function(){i.wrapper.ownerDocument.body.focus({preventScroll:!0}),i.input.focus()},20):i.input.focus())}),a=function(u){o=o||Math.abs(t.clientX-u.clientX)+Math.abs(t.clientY-u.clientY)>=10},s=function(){return o=!0};Y&&(i.scroller.draggable=!0),e.state.draggingText=l,l.copy=!r.moveOnDrag,Se(i.wrapper.ownerDocument,"mouseup",l),Se(i.wrapper.ownerDocument,"mousemove",a),Se(i.scroller,"dragstart",s),Se(i.scroller,"drop",l),e.state.delayingBlurEvent=!0,setTimeout(function(){return i.input.focus()},20),i.scroller.dragDrop&&i.scroller.dragDrop()}function na(e,t,n){if(n=="char")return new He(t,t);if(n=="word")return e.findWordAt(t);if(n=="line")return new He(B(t.line,0),Ae(e.doc,B(t.line+1,0)));var r=n(e,t);return new He(r.from,r.to)}function zu(e,t,n,r){k&&Hi(e);var i=e.display,o=e.doc;pt(t);var l,a,s=o.sel,u=s.ranges;if(r.addNew&&!r.extend?(a=o.sel.contains(n),a>-1?l=u[a]:l=new He(n,n)):(l=o.sel.primary(),a=o.sel.primIndex),r.unit=="rectangle")r.addNew||(l=new He(n,n)),n=Lr(e,t,!0,!0),a=-1;else{var h=na(e,n,r.unit);r.extend?l=$i(l,h.anchor,h.head,r.extend):l=h}r.addNew?a==-1?(a=u.length,gt(o,Kt(e,u.concat([l]),a),{scroll:!1,origin:"*mouse"})):u.length>1&&u[a].empty()&&r.unit=="char"&&!r.extend?(gt(o,Kt(e,u.slice(0,a).concat(u.slice(a+1)),0),{scroll:!1,origin:"*mouse"}),s=o.sel):eo(o,a,l,dt):(a=0,gt(o,new Ot([l],0),dt),s=o.sel);var x=n;function D(se){if(ce(x,se)!=0)if(x=se,r.unit=="rectangle"){for(var ge=[],Le=e.options.tabSize,ke=Fe(ye(o,n.line).text,n.ch,Le),Ee=Fe(ye(o,se.line).text,se.ch,Le),Ke=Math.min(ke,Ee),st=Math.max(ke,Ee),Xe=Math.min(n.line,se.line),Nt=Math.min(e.lastLine(),Math.max(n.line,se.line));Xe<=Nt;Xe++){var Tt=ye(o,Xe).text,tt=_e(Tt,Ke,Le);Ke==st?ge.push(new He(B(Xe,tt),B(Xe,tt))):Tt.length>tt&&ge.push(new He(B(Xe,tt),B(Xe,_e(Tt,st,Le))))}ge.length||ge.push(new He(n,n)),gt(o,Kt(e,s.ranges.slice(0,a).concat(ge),a),{origin:"*mouse",scroll:!1}),e.scrollIntoView(se)}else{var Ct=l,ft=na(e,se,r.unit),nt=Ct.anchor,rt;ce(ft.anchor,nt)>0?(rt=ft.head,nt=Wr(Ct.from(),ft.anchor)):(rt=ft.anchor,nt=wt(Ct.to(),ft.head));var Ze=s.ranges.slice(0);Ze[a]=Bu(e,new He(Ae(o,nt),rt)),gt(o,Kt(e,Ze,a),dt)}}var L=i.wrapper.getBoundingClientRect(),H=0;function Z(se){var ge=++H,Le=Lr(e,se,!0,r.unit=="rectangle");if(Le)if(ce(Le,x)!=0){e.curOp.focus=y(fe(e)),D(Le);var ke=$n(i,o);(Le.line>=ke.to||Le.lineL.bottom?20:0;Ee&&setTimeout(lt(e,function(){H==ge&&(i.scroller.scrollTop+=Ee,Z(se))}),50)}}function ie(se){e.state.selectingText=!1,H=1/0,se&&(pt(se),i.input.focus()),ht(i.wrapper.ownerDocument,"mousemove",ae),ht(i.wrapper.ownerDocument,"mouseup",he),o.history.lastSelOrigin=null}var ae=lt(e,function(se){se.buttons===0||!Rt(se)?ie(se):Z(se)}),he=lt(e,ie);e.state.selectingText=he,Se(i.wrapper.ownerDocument,"mousemove",ae),Se(i.wrapper.ownerDocument,"mouseup",he)}function Bu(e,t){var n=t.anchor,r=t.head,i=ye(e.doc,n.line);if(ce(n,r)==0&&n.sticky==r.sticky)return t;var o=Re(i);if(!o)return t;var l=lr(o,n.ch,n.sticky),a=o[l];if(a.from!=n.ch&&a.to!=n.ch)return t;var s=l+(a.from==n.ch==(a.level!=1)?0:1);if(s==0||s==o.length)return t;var u;if(r.line!=n.line)u=(r.line-n.line)*(e.doc.direction=="ltr"?1:-1)>0;else{var h=lr(o,r.ch,r.sticky),x=h-l||(r.ch-n.ch)*(a.level==1?-1:1);h==s-1||h==s?u=x<0:u=x>0}var D=o[s+(u?-1:0)],L=u==(D.level==1),H=L?D.from:D.to,Z=L?"after":"before";return n.ch==H&&n.sticky==Z?t:new He(new B(n.line,H,Z),r)}function ia(e,t,n,r){var i,o;if(t.touches)i=t.touches[0].clientX,o=t.touches[0].clientY;else try{i=t.clientX,o=t.clientY}catch{return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&pt(t);var l=e.display,a=l.lineDiv.getBoundingClientRect();if(o>a.bottom||!Ft(e,n))return kt(t);o-=a.top-l.viewOffset;for(var s=0;s=i){var h=m(e.doc,o),x=e.display.gutterSpecs[s];return Ye(e,n,e,h,x.className,t),kt(t)}}}function lo(e,t){return ia(e,t,"gutterClick",!0)}function oa(e,t){tr(e.display,t)||Ru(e,t)||Qe(e,t,"contextmenu")||J||e.display.input.onContextMenu(t)}function Ru(e,t){return Ft(e,"gutterContextMenu")?ia(e,t,"gutterContextMenu",!1):!1}function la(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),gn(e)}var tn={toString:function(){return"CodeMirror.Init"}},aa={},di={};function Wu(e){var t=e.optionHandlers;function n(r,i,o,l){e.defaults[r]=i,o&&(t[r]=l?function(a,s,u){u!=tn&&o(a,s,u)}:o)}e.defineOption=n,e.Init=tn,n("value","",function(r,i){return r.setValue(i)},!0),n("mode",null,function(r,i){r.doc.modeOption=i,Ji(r)},!0),n("indentUnit",2,Ji,!0),n("indentWithTabs",!1),n("smartIndent",!0),n("tabSize",4,function(r){Sn(r),gn(r),St(r)},!0),n("lineSeparator",null,function(r,i){if(r.doc.lineSep=i,!!i){var o=[],l=r.doc.first;r.doc.iter(function(s){for(var u=0;;){var h=s.text.indexOf(i,u);if(h==-1)break;u=h+i.length,o.push(B(l,h))}l++});for(var a=o.length-1;a>=0;a--)Zr(r.doc,i,o[a],B(o[a].line,o[a].ch+i.length))}}),n("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g,function(r,i,o){r.state.specialChars=new RegExp(i.source+(i.test(" ")?"":"| "),"g"),o!=tn&&r.refresh()}),n("specialCharPlaceholder",ps,function(r){return r.refresh()},!0),n("electricChars",!0),n("inputStyle",N?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},!0),n("spellcheck",!1,function(r,i){return r.getInputField().spellcheck=i},!0),n("autocorrect",!1,function(r,i){return r.getInputField().autocorrect=i},!0),n("autocapitalize",!1,function(r,i){return r.getInputField().autocapitalize=i},!0),n("rtlMoveVisually",!q),n("wholeLineUpdateBefore",!0),n("theme","default",function(r){la(r),wn(r)},!0),n("keyMap","default",function(r,i,o){var l=fi(i),a=o!=tn&&fi(o);a&&a.detach&&a.detach(r,l),l.attach&&l.attach(r,a||null)}),n("extraKeys",null),n("configureMouse",null),n("lineWrapping",!1,_u,!0),n("gutters",[],function(r,i){r.display.gutterSpecs=Yi(i,r.options.lineNumbers),wn(r)},!0),n("fixedGutter",!0,function(r,i){r.display.gutters.style.left=i?zi(r.display)+"px":"0",r.refresh()},!0),n("coverGutterNextToScrollbar",!1,function(r){return Xr(r)},!0),n("scrollbarStyle","native",function(r){ul(r),Xr(r),r.display.scrollbars.setScrollTop(r.doc.scrollTop),r.display.scrollbars.setScrollLeft(r.doc.scrollLeft)},!0),n("lineNumbers",!1,function(r,i){r.display.gutterSpecs=Yi(r.options.gutters,i),wn(r)},!0),n("firstLineNumber",1,wn,!0),n("lineNumberFormatter",function(r){return r},wn,!0),n("showCursorWhenSelecting",!1,vn,!0),n("resetSelectionOnContextMenu",!0),n("lineWiseCopyCut",!0),n("pasteLinesPerSelection",!0),n("selectionsMayTouch",!1),n("readOnly",!1,function(r,i){i=="nocursor"&&(Ur(r),r.display.input.blur()),r.display.input.readOnlyChanged(i)}),n("screenReaderLabel",null,function(r,i){i=i===""?null:i,r.display.input.screenReaderLabelChanged(i)}),n("disableInput",!1,function(r,i){i||r.display.input.reset()},!0),n("dragDrop",!0,Hu),n("allowDropFileTypes",null),n("cursorBlinkRate",530),n("cursorScrollMargin",0),n("cursorHeight",1,vn,!0),n("singleCursorHeightPerLine",!0,vn,!0),n("workTime",100),n("workDelay",100),n("flattenSpans",!0,Sn,!0),n("addModeClass",!1,Sn,!0),n("pollInterval",100),n("undoDepth",200,function(r,i){return r.doc.history.undoDepth=i}),n("historyEventDelay",1250),n("viewportMargin",10,function(r){return r.refresh()},!0),n("maxHighlightLength",1e4,Sn,!0),n("moveInputWithCursor",!0,function(r,i){i||r.display.input.resetPosition()}),n("tabindex",null,function(r,i){return r.display.input.getField().tabIndex=i||""}),n("autofocus",null),n("direction","ltr",function(r,i){return r.doc.setDirection(i)},!0),n("phrases",null)}function Hu(e,t,n){var r=n&&n!=tn;if(!t!=!r){var i=e.display.dragFunctions,o=t?Se:ht;o(e.display.scroller,"dragstart",i.start),o(e.display.scroller,"dragenter",i.enter),o(e.display.scroller,"dragover",i.over),o(e.display.scroller,"dragleave",i.leave),o(e.display.scroller,"drop",i.drop)}}function _u(e){e.options.lineWrapping?(j(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):($(e.display.wrapper,"CodeMirror-wrap"),Ci(e)),Bi(e),St(e),gn(e),setTimeout(function(){return Xr(e)},100)}function Ge(e,t){var n=this;if(!(this instanceof Ge))return new Ge(e,t);this.options=t=t?Me(t):{},Me(aa,t,!1);var r=t.value;typeof r=="string"?r=new Lt(r,t.mode,null,t.lineSeparator,t.direction):t.mode&&(r.modeOption=t.mode),this.doc=r;var i=new Ge.inputStyles[t.inputStyle](this),o=this.display=new eu(e,r,i,t);o.wrapper.CodeMirror=this,la(this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),ul(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new Ce,keySeq:null,specialChars:null},t.autofocus&&!N&&o.input.focus(),k&&I<11&&setTimeout(function(){return n.display.input.reset(!0)},20),qu(this),yu(),Mr(this),this.curOp.forceUpdate=!0,yl(this,r),t.autofocus&&!N||this.hasFocus()?setTimeout(function(){n.hasFocus()&&!n.state.focused&&_i(n)},20):Ur(this);for(var l in di)di.hasOwnProperty(l)&&di[l](this,t[l],tn);dl(this),t.finishInit&&t.finishInit(this);for(var a=0;a400}Se(t.scroller,"touchstart",function(s){if(!Qe(e,s)&&!o(s)&&!lo(e,s)){t.input.ensurePolled(),clearTimeout(n);var u=+new Date;t.activeTouch={start:u,moved:!1,prev:u-r.end<=300?r:null},s.touches.length==1&&(t.activeTouch.left=s.touches[0].pageX,t.activeTouch.top=s.touches[0].pageY)}}),Se(t.scroller,"touchmove",function(){t.activeTouch&&(t.activeTouch.moved=!0)}),Se(t.scroller,"touchend",function(s){var u=t.activeTouch;if(u&&!tr(t,s)&&u.left!=null&&!u.moved&&new Date-u.start<300){var h=e.coordsChar(t.activeTouch,"page"),x;!u.prev||l(u,u.prev)?x=new He(h,h):!u.prev.prev||l(u,u.prev.prev)?x=e.findWordAt(h):x=new He(B(h.line,0),Ae(e.doc,B(h.line+1,0))),e.setSelection(x.anchor,x.head),e.focus(),pt(s)}i()}),Se(t.scroller,"touchcancel",i),Se(t.scroller,"scroll",function(){t.scroller.clientHeight&&(xn(e,t.scroller.scrollTop),Cr(e,t.scroller.scrollLeft,!0),Ye(e,"scroll",e))}),Se(t.scroller,"mousewheel",function(s){return gl(e,s)}),Se(t.scroller,"DOMMouseScroll",function(s){return gl(e,s)}),Se(t.wrapper,"scroll",function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0}),t.dragFunctions={enter:function(s){Qe(e,s)||ar(s)},over:function(s){Qe(e,s)||(xu(e,s),ar(s))},start:function(s){return mu(e,s)},drop:lt(e,vu),leave:function(s){Qe(e,s)||jl(e)}};var a=t.input.getField();Se(a,"keyup",function(s){return ea.call(e,s)}),Se(a,"keydown",lt(e,$l)),Se(a,"keypress",lt(e,ta)),Se(a,"focus",function(s){return _i(e,s)}),Se(a,"blur",function(s){return Ur(e,s)})}var ao=[];Ge.defineInitHook=function(e){return ao.push(e)};function zn(e,t,n,r){var i=e.doc,o;n==null&&(n="add"),n=="smart"&&(i.mode.indent?o=fn(e,t).state:n="prev");var l=e.options.tabSize,a=ye(i,t),s=Fe(a.text,null,l);a.stateAfter&&(a.stateAfter=null);var u=a.text.match(/^\s*/)[0],h;if(!r&&!/\S/.test(a.text))h=0,n="not";else if(n=="smart"&&(h=i.mode.indent(o,a.text.slice(u.length),a.text),h==qe||h>150)){if(!r)return;n="prev"}n=="prev"?t>i.first?h=Fe(ye(i,t-1).text,null,l):h=0:n=="add"?h=s+e.options.indentUnit:n=="subtract"?h=s-e.options.indentUnit:typeof n=="number"&&(h=s+n),h=Math.max(0,h);var x="",D=0;if(e.options.indentWithTabs)for(var L=Math.floor(h/l);L;--L)D+=l,x+=" ";if(Dl,s=zt(t),u=null;if(a&&r.ranges.length>1)if(Ut&&Ut.text.join(` -`)==t){if(r.ranges.length%Ut.text.length==0){u=[];for(var h=0;h=0;D--){var L=r.ranges[D],H=L.from(),Z=L.to();L.empty()&&(n&&n>0?H=B(H.line,H.ch-n):e.state.overwrite&&!a?Z=B(Z.line,Math.min(ye(o,Z.line).text.length,Z.ch+we(s).length)):a&&Ut&&Ut.lineWise&&Ut.text.join(` -`)==s.join(` -`)&&(H=Z=B(H.line,0)));var ie={from:H,to:Z,text:u?u[D%u.length]:s,origin:i||(a?"paste":e.state.cutIncoming>l?"cut":"+input")};Jr(e.doc,ie),ot(e,"inputRead",e,ie)}t&&!a&&ua(e,t),Gr(e),e.curOp.updateInput<2&&(e.curOp.updateInput=x),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function sa(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");if(n)return e.preventDefault(),!t.isReadOnly()&&!t.options.disableInput&&t.hasFocus()&&At(t,function(){return so(t,n,0,null,"paste")}),!0}function ua(e,t){if(!(!e.options.electricChars||!e.options.smartIndent))for(var n=e.doc.sel,r=n.ranges.length-1;r>=0;r--){var i=n.ranges[r];if(!(i.head.ch>100||r&&n.ranges[r-1].head.line==i.head.line)){var o=e.getModeAt(i.head),l=!1;if(o.electricChars){for(var a=0;a-1){l=zn(e,i.head.line,"smart");break}}else o.electricInput&&o.electricInput.test(ye(e.doc,i.head.line).text.slice(0,i.head.ch))&&(l=zn(e,i.head.line,"smart"));l&&ot(e,"electricInput",e,i.head.line)}}}function fa(e){for(var t=[],n=[],r=0;ro&&(zn(this,a.head.line,r,!0),o=a.head.line,l==this.doc.sel.primIndex&&Gr(this));else{var s=a.from(),u=a.to(),h=Math.max(o,s.line);o=Math.min(this.lastLine(),u.line-(u.ch?0:1))+1;for(var x=h;x0&&eo(this.doc,l,new He(s,D[l].to()),Ve)}}}),getTokenAt:function(r,i){return ko(this,r,i)},getLineTokens:function(r,i){return ko(this,B(r),i,!0)},getTokenTypeAt:function(r){r=Ae(this.doc,r);var i=xo(this,ye(this.doc,r.line)),o=0,l=(i.length-1)/2,a=r.ch,s;if(a==0)s=i[2];else for(;;){var u=o+l>>1;if((u?i[u*2-1]:0)>=a)l=u;else if(i[u*2+1]s&&(r=s,l=!0),a=ye(this.doc,r)}else a=r;return Yn(this,a,{top:0,left:0},i||"page",o||l).top+(l?this.doc.height-er(a):0)},defaultTextHeight:function(){return jr(this.display)},defaultCharWidth:function(){return Kr(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(r,i,o,l,a){var s=this.display;r=jt(this,Ae(this.doc,r));var u=r.bottom,h=r.left;if(i.style.position="absolute",i.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(i),s.sizer.appendChild(i),l=="over")u=r.top;else if(l=="above"||l=="near"){var x=Math.max(s.wrapper.clientHeight,this.doc.height),D=Math.max(s.sizer.clientWidth,s.lineSpace.clientWidth);(l=="above"||r.bottom+i.offsetHeight>x)&&r.top>i.offsetHeight?u=r.top-i.offsetHeight:r.bottom+i.offsetHeight<=x&&(u=r.bottom),h+i.offsetWidth>D&&(h=D-i.offsetWidth)}i.style.top=u+"px",i.style.left=i.style.right="",a=="right"?(h=s.sizer.clientWidth-i.offsetWidth,i.style.right="0px"):(a=="left"?h=0:a=="middle"&&(h=(s.sizer.clientWidth-i.offsetWidth)/2),i.style.left=h+"px"),o&&Hs(this,{left:h,top:u,right:h+i.offsetWidth,bottom:u+i.offsetHeight})},triggerOnKeyDown:yt($l),triggerOnKeyPress:yt(ta),triggerOnKeyUp:ea,triggerOnMouseDown:yt(ra),execCommand:function(r){if(En.hasOwnProperty(r))return En[r].call(null,this)},triggerElectric:yt(function(r){ua(this,r)}),findPosH:function(r,i,o,l){var a=1;i<0&&(a=-1,i=-i);for(var s=Ae(this.doc,r),u=0;u0&&h(o.charAt(l-1));)--l;for(;a.5||this.options.lineWrapping)&&Bi(this),Ye(this,"refresh",this)}),swapDoc:yt(function(r){var i=this.doc;return i.cm=null,this.state.selectingText&&this.state.selectingText(),yl(this,r),gn(this),this.display.input.reset(),mn(this,r.scrollLeft,r.scrollTop),this.curOp.forceScroll=!0,ot(this,"swapDoc",this,i),i}),phrase:function(r){var i=this.options.phrases;return i&&Object.prototype.hasOwnProperty.call(i,r)?i[r]:r},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},Bt(e),e.registerHelper=function(r,i,o){n.hasOwnProperty(r)||(n[r]=e[r]={_global:[]}),n[r][i]=o},e.registerGlobalHelper=function(r,i,o,l){e.registerHelper(r,i,l),n[r]._global.push({pred:o,val:l})}}function fo(e,t,n,r,i){var o=t,l=n,a=ye(e,t.line),s=i&&e.direction=="rtl"?-n:n;function u(){var he=t.line+s;return he=e.first+e.size?!1:(t=new B(he,t.ch,t.sticky),a=ye(e,he))}function h(he){var se;if(r=="codepoint"){var ge=a.text.charCodeAt(t.ch+(n>0?0:-1));if(isNaN(ge))se=null;else{var Le=n>0?ge>=55296&&ge<56320:ge>=56320&&ge<57343;se=new B(t.line,Math.max(0,Math.min(a.text.length,t.ch+n*(Le?2:1))),-n)}}else i?se=Lu(e.cm,a,t,n):se=ro(a,t,n);if(se==null)if(!he&&u())t=no(i,e.cm,a,t.line,s);else return!1;else t=se;return!0}if(r=="char"||r=="codepoint")h();else if(r=="column")h(!0);else if(r=="word"||r=="group")for(var x=null,D=r=="group",L=e.cm&&e.cm.getHelper(t,"wordChars"),H=!0;!(n<0&&!h(!H));H=!1){var Z=a.text.charAt(t.ch)||` -`,ie=De(Z,L)?"w":D&&Z==` -`?"n":!D||/\s/.test(Z)?null:"p";if(D&&!H&&!ie&&(ie="s"),x&&x!=ie){n<0&&(n=1,h(),t.sticky="after");break}if(ie&&(x=ie),n>0&&!h(!H))break}var ae=ai(e,t,o,l,!0);return We(o,ae)&&(ae.hitSide=!0),ae}function da(e,t,n,r){var i=e.doc,o=t.left,l;if(r=="page"){var a=Math.min(e.display.wrapper.clientHeight,le(e).innerHeight||i(e).documentElement.clientHeight),s=Math.max(a-.5*jr(e.display),3);l=(n>0?t.bottom:t.top)+n*s}else r=="line"&&(l=n>0?t.bottom+3:t.top-3);for(var u;u=Oi(e,o,l),!!u.outside;){if(n<0?l<=0:l>=i.height){u.hitSide=!0;break}l+=n*5}return u}var je=function(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new Ce,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null};je.prototype.init=function(e){var t=this,n=this,r=n.cm,i=n.div=e.lineDiv;i.contentEditable=!0,uo(i,r.options.spellcheck,r.options.autocorrect,r.options.autocapitalize);function o(a){for(var s=a.target;s;s=s.parentNode){if(s==i)return!0;if(/\bCodeMirror-(?:line)?widget\b/.test(s.className))break}return!1}Se(i,"paste",function(a){!o(a)||Qe(r,a)||sa(a,r)||I<=11&&setTimeout(lt(r,function(){return t.updateFromDOM()}),20)}),Se(i,"compositionstart",function(a){t.composing={data:a.data,done:!1}}),Se(i,"compositionupdate",function(a){t.composing||(t.composing={data:a.data,done:!1})}),Se(i,"compositionend",function(a){t.composing&&(a.data!=t.composing.data&&t.readFromDOMSoon(),t.composing.done=!0)}),Se(i,"touchstart",function(){return n.forceCompositionEnd()}),Se(i,"input",function(){t.composing||t.readFromDOMSoon()});function l(a){if(!(!o(a)||Qe(r,a))){if(r.somethingSelected())hi({lineWise:!1,text:r.getSelections()}),a.type=="cut"&&r.replaceSelection("",null,"cut");else if(r.options.lineWiseCopyCut){var s=fa(r);hi({lineWise:!0,text:s.text}),a.type=="cut"&&r.operation(function(){r.setSelections(s.ranges,0,Ve),r.replaceSelection("",null,"cut")})}else return;if(a.clipboardData){a.clipboardData.clearData();var u=Ut.text.join(` -`);if(a.clipboardData.setData("Text",u),a.clipboardData.getData("Text")==u){a.preventDefault();return}}var h=ca(),x=h.firstChild;uo(x),r.display.lineSpace.insertBefore(h,r.display.lineSpace.firstChild),x.value=Ut.text.join(` -`);var D=y(Te(i));v(x),setTimeout(function(){r.display.lineSpace.removeChild(h),D.focus(),D==i&&n.showPrimarySelection()},50)}}Se(i,"copy",l),Se(i,"cut",l)},je.prototype.screenReaderLabelChanged=function(e){e?this.div.setAttribute("aria-label",e):this.div.removeAttribute("aria-label")},je.prototype.prepareSelection=function(){var e=rl(this.cm,!1);return e.focus=y(Te(this.div))==this.div,e},je.prototype.showSelection=function(e,t){!e||!this.cm.display.view.length||((e.focus||t)&&this.showPrimarySelection(),this.showMultipleSelections(e))},je.prototype.getSelection=function(){return this.cm.display.wrapper.ownerDocument.getSelection()},je.prototype.showPrimarySelection=function(){var e=this.getSelection(),t=this.cm,n=t.doc.sel.primary(),r=n.from(),i=n.to();if(t.display.viewTo==t.display.viewFrom||r.line>=t.display.viewTo||i.line=t.display.viewFrom&&ha(t,r)||{node:a[0].measure.map[2],offset:0},u=i.linee.firstLine()&&(r=B(r.line-1,ye(e.doc,r.line-1).length)),i.ch==ye(e.doc,i.line).text.length&&i.linet.viewTo-1)return!1;var o,l,a;r.line==t.viewFrom||(o=Tr(e,r.line))==0?(l=f(t.view[0].line),a=t.view[0].node):(l=f(t.view[o].line),a=t.view[o-1].node.nextSibling);var s=Tr(e,i.line),u,h;if(s==t.view.length-1?(u=t.viewTo-1,h=t.lineDiv.lastChild):(u=f(t.view[s+1].line)-1,h=t.view[s+1].node.previousSibling),!a)return!1;for(var x=e.doc.splitLines(Uu(e,a,h,l,u)),D=Vt(e.doc,B(l,0),B(u,ye(e.doc,u).text.length));x.length>1&&D.length>1;)if(we(x)==we(D))x.pop(),D.pop(),u--;else if(x[0]==D[0])x.shift(),D.shift(),l++;else break;for(var L=0,H=0,Z=x[0],ie=D[0],ae=Math.min(Z.length,ie.length);Lr.ch&&he.charCodeAt(he.length-H-1)==se.charCodeAt(se.length-H-1);)L--,H++;x[x.length-1]=he.slice(0,he.length-H).replace(/^\u200b+/,""),x[0]=x[0].slice(L).replace(/\u200b+$/,"");var Le=B(l,L),ke=B(u,D.length?we(D).length-H:0);if(x.length>1||x[0]||ce(Le,ke))return Zr(e.doc,x,Le,ke,"+input"),!0},je.prototype.ensurePolled=function(){this.forceCompositionEnd()},je.prototype.reset=function(){this.forceCompositionEnd()},je.prototype.forceCompositionEnd=function(){this.composing&&(clearTimeout(this.readDOMTimeout),this.composing=null,this.updateFromDOM(),this.div.blur(),this.div.focus())},je.prototype.readFromDOMSoon=function(){var e=this;this.readDOMTimeout==null&&(this.readDOMTimeout=setTimeout(function(){if(e.readDOMTimeout=null,e.composing)if(e.composing.done)e.composing=null;else return;e.updateFromDOM()},80))},je.prototype.updateFromDOM=function(){var e=this;(this.cm.isReadOnly()||!this.pollContent())&&At(this.cm,function(){return St(e.cm)})},je.prototype.setUneditable=function(e){e.contentEditable="false"},je.prototype.onKeyPress=function(e){e.charCode==0||this.composing||(e.preventDefault(),this.cm.isReadOnly()||lt(this.cm,so)(this.cm,String.fromCharCode(e.charCode==null?e.keyCode:e.charCode),0))},je.prototype.readOnlyChanged=function(e){this.div.contentEditable=String(e!="nocursor")},je.prototype.onContextMenu=function(){},je.prototype.resetPosition=function(){},je.prototype.needsContentAttribute=!0;function ha(e,t){var n=Ai(e,t.line);if(!n||n.hidden)return null;var r=ye(e.doc,t.line),i=qo(n,r,t.line),o=Re(r,e.doc.direction),l="left";if(o){var a=lr(o,t.ch);l=a%2?"right":"left"}var s=Uo(i.map,t.ch,l);return s.offset=s.collapse=="right"?s.end:s.start,s}function Ku(e){for(var t=e;t;t=t.parentNode)if(/CodeMirror-gutter-wrapper/.test(t.className))return!0;return!1}function rn(e,t){return t&&(e.bad=!0),e}function Uu(e,t,n,r,i){var o="",l=!1,a=e.doc.lineSeparator(),s=!1;function u(L){return function(H){return H.id==L}}function h(){l&&(o+=a,s&&(o+=a),l=s=!1)}function x(L){L&&(h(),o+=L)}function D(L){if(L.nodeType==1){var H=L.getAttribute("cm-text");if(H){x(H);return}var Z=L.getAttribute("cm-marker"),ie;if(Z){var ae=e.findMarks(B(r,0),B(i+1,0),u(+Z));ae.length&&(ie=ae[0].find(0))&&x(Vt(e.doc,ie.from,ie.to).join(a));return}if(L.getAttribute("contenteditable")=="false")return;var he=/^(pre|div|p|li|table|br)$/i.test(L.nodeName);if(!/^br$/i.test(L.nodeName)&&L.textContent.length==0)return;he&&h();for(var se=0;se=9&&t.hasSelection&&(t.hasSelection=null),n.poll()}),Se(i,"paste",function(l){Qe(r,l)||sa(l,r)||(r.state.pasteIncoming=+new Date,n.fastPoll())});function o(l){if(!Qe(r,l)){if(r.somethingSelected())hi({lineWise:!1,text:r.getSelections()});else if(r.options.lineWiseCopyCut){var a=fa(r);hi({lineWise:!0,text:a.text}),l.type=="cut"?r.setSelections(a.ranges,null,Ve):(n.prevInput="",i.value=a.text.join(` -`),v(i))}else return;l.type=="cut"&&(r.state.cutIncoming=+new Date)}}Se(i,"cut",o),Se(i,"copy",o),Se(e.scroller,"paste",function(l){if(!(tr(e,l)||Qe(r,l))){if(!i.dispatchEvent){r.state.pasteIncoming=+new Date,n.focus();return}var a=new Event("paste");a.clipboardData=l.clipboardData,i.dispatchEvent(a)}}),Se(e.lineSpace,"selectstart",function(l){tr(e,l)||pt(l)}),Se(i,"compositionstart",function(){var l=r.getCursor("from");n.composing&&n.composing.range.clear(),n.composing={start:l,range:r.markText(l,r.getCursor("to"),{className:"CodeMirror-composing"})}}),Se(i,"compositionend",function(){n.composing&&(n.poll(),n.composing.range.clear(),n.composing=null)})},$e.prototype.createField=function(e){this.wrapper=ca(),this.textarea=this.wrapper.firstChild;var t=this.cm.options;uo(this.textarea,t.spellcheck,t.autocorrect,t.autocapitalize)},$e.prototype.screenReaderLabelChanged=function(e){e?this.textarea.setAttribute("aria-label",e):this.textarea.removeAttribute("aria-label")},$e.prototype.prepareSelection=function(){var e=this.cm,t=e.display,n=e.doc,r=rl(e);if(e.options.moveInputWithCursor){var i=jt(e,n.sel.primary().head,"div"),o=t.wrapper.getBoundingClientRect(),l=t.lineDiv.getBoundingClientRect();r.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,i.top+l.top-o.top)),r.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,i.left+l.left-o.left))}return r},$e.prototype.showSelection=function(e){var t=this.cm,n=t.display;G(n.cursorDiv,e.cursors),G(n.selectionDiv,e.selection),e.teTop!=null&&(this.wrapper.style.top=e.teTop+"px",this.wrapper.style.left=e.teLeft+"px")},$e.prototype.reset=function(e){if(!(this.contextMenuPending||this.composing&&e)){var t=this.cm;if(this.resetting=!0,t.somethingSelected()){this.prevInput="";var n=t.getSelection();this.textarea.value=n,t.state.focused&&v(this.textarea),k&&I>=9&&(this.hasSelection=n)}else e||(this.prevInput=this.textarea.value="",k&&I>=9&&(this.hasSelection=null));this.resetting=!1}},$e.prototype.getField=function(){return this.textarea},$e.prototype.supportsTouch=function(){return!1},$e.prototype.focus=function(){if(this.cm.options.readOnly!="nocursor"&&(!N||y(Te(this.textarea))!=this.textarea))try{this.textarea.focus()}catch{}},$e.prototype.blur=function(){this.textarea.blur()},$e.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0},$e.prototype.receivedFocus=function(){this.slowPoll()},$e.prototype.slowPoll=function(){var e=this;this.pollingFast||this.polling.set(this.cm.options.pollInterval,function(){e.poll(),e.cm.state.focused&&e.slowPoll()})},$e.prototype.fastPoll=function(){var e=!1,t=this;t.pollingFast=!0;function n(){var r=t.poll();!r&&!e?(e=!0,t.polling.set(60,n)):(t.pollingFast=!1,t.slowPoll())}t.polling.set(20,n)},$e.prototype.poll=function(){var e=this,t=this.cm,n=this.textarea,r=this.prevInput;if(this.contextMenuPending||this.resetting||!t.state.focused||ur(n)&&!r&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq)return!1;var i=n.value;if(i==r&&!t.somethingSelected())return!1;if(k&&I>=9&&this.hasSelection===i||z&&/[\uf700-\uf7ff]/.test(i))return t.display.input.reset(),!1;if(t.doc.sel==t.display.selForContextMenu){var o=i.charCodeAt(0);if(o==8203&&!r&&(r="​"),o==8666)return this.reset(),this.cm.execCommand("undo")}for(var l=0,a=Math.min(r.length,i.length);l1e3||i.indexOf(` -`)>-1?n.value=e.prevInput="":e.prevInput=i,e.composing&&(e.composing.range.clear(),e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"}))}),!0},$e.prototype.ensurePolled=function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},$e.prototype.onKeyPress=function(){k&&I>=9&&(this.hasSelection=null),this.fastPoll()},$e.prototype.onContextMenu=function(e){var t=this,n=t.cm,r=n.display,i=t.textarea;t.contextMenuPending&&t.contextMenuPending();var o=Lr(n,e),l=r.scroller.scrollTop;if(!o||A)return;var a=n.options.resetSelectionOnContextMenu;a&&n.doc.sel.contains(o)==-1&<(n,gt)(n.doc,pr(o),Ve);var s=i.style.cssText,u=t.wrapper.style.cssText,h=t.wrapper.offsetParent.getBoundingClientRect();t.wrapper.style.cssText="position: static",i.style.cssText=`position: absolute; width: 30px; height: 30px; - top: `+(e.clientY-h.top-5)+"px; left: "+(e.clientX-h.left-5)+`px; - z-index: 1000; background: `+(k?"rgba(255, 255, 255, .05)":"transparent")+`; - outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);`;var x;Y&&(x=i.ownerDocument.defaultView.scrollY),r.input.focus(),Y&&i.ownerDocument.defaultView.scrollTo(null,x),r.input.reset(),n.somethingSelected()||(i.value=t.prevInput=" "),t.contextMenuPending=L,r.selForContextMenu=n.doc.sel,clearTimeout(r.detectingSelectAll);function D(){if(i.selectionStart!=null){var Z=n.somethingSelected(),ie="​"+(Z?i.value:"");i.value="⇚",i.value=ie,t.prevInput=Z?"":"​",i.selectionStart=1,i.selectionEnd=ie.length,r.selForContextMenu=n.doc.sel}}function L(){if(t.contextMenuPending==L&&(t.contextMenuPending=!1,t.wrapper.style.cssText=u,i.style.cssText=s,k&&I<9&&r.scrollbars.setScrollTop(r.scroller.scrollTop=l),i.selectionStart!=null)){(!k||k&&I<9)&&D();var Z=0,ie=function(){r.selForContextMenu==n.doc.sel&&i.selectionStart==0&&i.selectionEnd>0&&t.prevInput=="​"?lt(n,El)(n):Z++<10?r.detectingSelectAll=setTimeout(ie,500):(r.selForContextMenu=null,r.input.reset())};r.detectingSelectAll=setTimeout(ie,200)}}if(k&&I>=9&&D(),J){ar(e);var H=function(){ht(window,"mouseup",H),setTimeout(L,20)};Se(window,"mouseup",H)}else setTimeout(L,50)},$e.prototype.readOnlyChanged=function(e){e||this.reset(),this.textarea.disabled=e=="nocursor",this.textarea.readOnly=!!e},$e.prototype.setUneditable=function(){},$e.prototype.needsContentAttribute=!1;function Xu(e,t){if(t=t?Me(t):{},t.value=e.value,!t.tabindex&&e.tabIndex&&(t.tabindex=e.tabIndex),!t.placeholder&&e.placeholder&&(t.placeholder=e.placeholder),t.autofocus==null){var n=y(Te(e));t.autofocus=n==e||e.getAttribute("autofocus")!=null&&n==document.body}function r(){e.value=a.getValue()}var i;if(e.form&&(Se(e.form,"submit",r),!t.leaveSubmitMethodAlone)){var o=e.form;i=o.submit;try{var l=o.submit=function(){r(),o.submit=i,o.submit(),o.submit=l}}catch{}}t.finishInit=function(s){s.save=r,s.getTextArea=function(){return e},s.toTextArea=function(){s.toTextArea=isNaN,r(),e.parentNode.removeChild(s.getWrapperElement()),e.style.display="",e.form&&(ht(e.form,"submit",r),!t.leaveSubmitMethodAlone&&typeof e.form.submit=="function"&&(e.form.submit=i))}},e.style.display="none";var a=Ge(function(s){return e.parentNode.insertBefore(s,e.nextSibling)},t);return a}function Yu(e){e.off=ht,e.on=Se,e.wheelEventPixels=tu,e.Doc=Lt,e.splitLines=zt,e.countColumn=Fe,e.findColumn=_e,e.isWordChar=me,e.Pass=qe,e.signal=Ye,e.Line=Hr,e.changeEnd=gr,e.scrollbarModel=sl,e.Pos=B,e.cmpPos=ce,e.modes=Pr,e.mimeModes=Ht,e.resolveMode=Ir,e.getMode=zr,e.modeExtensions=fr,e.extendMode=Br,e.copyState=Gt,e.startState=Rr,e.innerMode=sn,e.commands=En,e.keyMap=nr,e.keyName=Yl,e.isModifierKey=Gl,e.lookupKey=$r,e.normalizeKeyMap=Su,e.StringStream=Je,e.SharedTextMarker=Fn,e.TextMarker=mr,e.LineWidget=Mn,e.e_preventDefault=pt,e.e_stopPropagation=Er,e.e_stop=ar,e.addClass=j,e.contains=g,e.rmClass=$,e.keyNames=xr}Wu(Ge),ju(Ge);var Qu="iter insert remove copy getEditor constructor".split(" ");for(var gi in Lt.prototype)Lt.prototype.hasOwnProperty(gi)&&ve(Qu,gi)<0&&(Ge.prototype[gi]=(function(e){return function(){return e.apply(this.doc,arguments)}})(Lt.prototype[gi]));return Bt(Lt),Ge.inputStyles={textarea:$e,contenteditable:je},Ge.defineMode=function(e){!Ge.defaults.mode&&e!="null"&&(Ge.defaults.mode=e),_t.apply(this,arguments)},Ge.defineMIME=kr,Ge.defineMode("null",function(){return{token:function(e){return e.skipToEnd()}}}),Ge.defineMIME("text/plain","null"),Ge.defineExtension=function(e,t){Ge.prototype[e]=t},Ge.defineDocExtension=function(e,t){Lt.prototype[e]=t},Ge.fromTextArea=Xu,Yu(Ge),Ge.version="5.65.18",Ge}))})(vi)),vi.exports}var Vu=mt();const df=Ju(Vu);var ga={exports:{}},va;function Xa(){return va||(va=1,(function(ct,xt){(function(b){b(mt())})(function(b){b.defineMode("css",function(J,P){var $=P.inline;P.propertyKeywords||(P=b.resolveMode("text/css"));var F=J.indentUnit,G=P.tokenHooks,c=P.documentTypes||{},T=P.mediaTypes||{},C=P.mediaFeatures||{},g=P.mediaValueKeywords||{},y=P.propertyKeywords||{},j=P.nonStandardPropertyKeywords||{},de=P.fontProperties||{},v=P.counterDescriptors||{},d=P.colorKeywords||{},fe=P.valueKeywords||{},Te=P.allowNested,le=P.lineComment,xe=P.supportsAtComponent===!0,Me=J.highlightNonStandardPropertyKeywords!==!1,Fe,Ce;function ve(E,ee){return Fe=ee,E}function Oe(E,ee){var K=E.next();if(G[K]){var ze=G[K](E,ee);if(ze!==!1)return ze}if(K=="@")return E.eatWhile(/[\w\\\-]/),ve("def",E.current());if(K=="="||(K=="~"||K=="|")&&E.eat("="))return ve(null,"compare");if(K=='"'||K=="'")return ee.tokenize=qe(K),ee.tokenize(E,ee);if(K=="#")return E.eatWhile(/[\w\\\-]/),ve("atom","hash");if(K=="!")return E.match(/^\s*\w*/),ve("keyword","important");if(/\d/.test(K)||K=="."&&E.eat(/\d/))return E.eatWhile(/[\w.%]/),ve("number","unit");if(K==="-"){if(/[\d.]/.test(E.peek()))return E.eatWhile(/[\w.%]/),ve("number","unit");if(E.match(/^-[\w\\\-]*/))return E.eatWhile(/[\w\\\-]/),E.match(/^\s*:/,!1)?ve("variable-2","variable-definition"):ve("variable-2","variable");if(E.match(/^\w+-/))return ve("meta","meta")}else return/[,+>*\/]/.test(K)?ve(null,"select-op"):K=="."&&E.match(/^-?[_a-z][_a-z0-9-]*/i)?ve("qualifier","qualifier"):/[:;{}\[\]\(\)]/.test(K)?ve(null,K):E.match(/^[\w-.]+(?=\()/)?(/^(url(-prefix)?|domain|regexp)$/i.test(E.current())&&(ee.tokenize=Ve),ve("variable callee","variable")):/[\w\\\-]/.test(K)?(E.eatWhile(/[\w\\\-]/),ve("property","word")):ve(null,null)}function qe(E){return function(ee,K){for(var ze=!1,me;(me=ee.next())!=null;){if(me==E&&!ze){E==")"&&ee.backUp(1);break}ze=!ze&&me=="\\"}return(me==E||!ze&&E!=")")&&(K.tokenize=null),ve("string","string")}}function Ve(E,ee){return E.next(),E.match(/^\s*[\"\')]/,!1)?ee.tokenize=null:ee.tokenize=qe(")"),ve(null,"(")}function dt(E,ee,K){this.type=E,this.indent=ee,this.prev=K}function Pe(E,ee,K,ze){return E.context=new dt(K,ee.indentation()+(ze===!1?0:F),E.context),K}function _e(E){return E.context.prev&&(E.context=E.context.prev),E.context.type}function Ue(E,ee,K){return Ie[K.context.type](E,ee,K)}function et(E,ee,K,ze){for(var me=ze||1;me>0;me--)K.context=K.context.prev;return Ue(E,ee,K)}function we(E){var ee=E.current().toLowerCase();fe.hasOwnProperty(ee)?Ce="atom":d.hasOwnProperty(ee)?Ce="keyword":Ce="variable"}var Ie={};return Ie.top=function(E,ee,K){if(E=="{")return Pe(K,ee,"block");if(E=="}"&&K.context.prev)return _e(K);if(xe&&/@component/i.test(E))return Pe(K,ee,"atComponentBlock");if(/^@(-moz-)?document$/i.test(E))return Pe(K,ee,"documentTypes");if(/^@(media|supports|(-moz-)?document|import)$/i.test(E))return Pe(K,ee,"atBlock");if(/^@(font-face|counter-style)/i.test(E))return K.stateArg=E,"restricted_atBlock_before";if(/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(E))return"keyframes";if(E&&E.charAt(0)=="@")return Pe(K,ee,"at");if(E=="hash")Ce="builtin";else if(E=="word")Ce="tag";else{if(E=="variable-definition")return"maybeprop";if(E=="interpolation")return Pe(K,ee,"interpolation");if(E==":")return"pseudo";if(Te&&E=="(")return Pe(K,ee,"parens")}return K.context.type},Ie.block=function(E,ee,K){if(E=="word"){var ze=ee.current().toLowerCase();return y.hasOwnProperty(ze)?(Ce="property","maybeprop"):j.hasOwnProperty(ze)?(Ce=Me?"string-2":"property","maybeprop"):Te?(Ce=ee.match(/^\s*:(?:\s|$)/,!1)?"property":"tag","block"):(Ce+=" error","maybeprop")}else return E=="meta"?"block":!Te&&(E=="hash"||E=="qualifier")?(Ce="error","block"):Ie.top(E,ee,K)},Ie.maybeprop=function(E,ee,K){return E==":"?Pe(K,ee,"prop"):Ue(E,ee,K)},Ie.prop=function(E,ee,K){if(E==";")return _e(K);if(E=="{"&&Te)return Pe(K,ee,"propBlock");if(E=="}"||E=="{")return et(E,ee,K);if(E=="(")return Pe(K,ee,"parens");if(E=="hash"&&!/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(ee.current()))Ce+=" error";else if(E=="word")we(ee);else if(E=="interpolation")return Pe(K,ee,"interpolation");return"prop"},Ie.propBlock=function(E,ee,K){return E=="}"?_e(K):E=="word"?(Ce="property","maybeprop"):K.context.type},Ie.parens=function(E,ee,K){return E=="{"||E=="}"?et(E,ee,K):E==")"?_e(K):E=="("?Pe(K,ee,"parens"):E=="interpolation"?Pe(K,ee,"interpolation"):(E=="word"&&we(ee),"parens")},Ie.pseudo=function(E,ee,K){return E=="meta"?"pseudo":E=="word"?(Ce="variable-3",K.context.type):Ue(E,ee,K)},Ie.documentTypes=function(E,ee,K){return E=="word"&&c.hasOwnProperty(ee.current())?(Ce="tag",K.context.type):Ie.atBlock(E,ee,K)},Ie.atBlock=function(E,ee,K){if(E=="(")return Pe(K,ee,"atBlock_parens");if(E=="}"||E==";")return et(E,ee,K);if(E=="{")return _e(K)&&Pe(K,ee,Te?"block":"top");if(E=="interpolation")return Pe(K,ee,"interpolation");if(E=="word"){var ze=ee.current().toLowerCase();ze=="only"||ze=="not"||ze=="and"||ze=="or"?Ce="keyword":T.hasOwnProperty(ze)?Ce="attribute":C.hasOwnProperty(ze)?Ce="property":g.hasOwnProperty(ze)?Ce="keyword":y.hasOwnProperty(ze)?Ce="property":j.hasOwnProperty(ze)?Ce=Me?"string-2":"property":fe.hasOwnProperty(ze)?Ce="atom":d.hasOwnProperty(ze)?Ce="keyword":Ce="error"}return K.context.type},Ie.atComponentBlock=function(E,ee,K){return E=="}"?et(E,ee,K):E=="{"?_e(K)&&Pe(K,ee,Te?"block":"top",!1):(E=="word"&&(Ce="error"),K.context.type)},Ie.atBlock_parens=function(E,ee,K){return E==")"?_e(K):E=="{"||E=="}"?et(E,ee,K,2):Ie.atBlock(E,ee,K)},Ie.restricted_atBlock_before=function(E,ee,K){return E=="{"?Pe(K,ee,"restricted_atBlock"):E=="word"&&K.stateArg=="@counter-style"?(Ce="variable","restricted_atBlock_before"):Ue(E,ee,K)},Ie.restricted_atBlock=function(E,ee,K){return E=="}"?(K.stateArg=null,_e(K)):E=="word"?(K.stateArg=="@font-face"&&!de.hasOwnProperty(ee.current().toLowerCase())||K.stateArg=="@counter-style"&&!v.hasOwnProperty(ee.current().toLowerCase())?Ce="error":Ce="property","maybeprop"):"restricted_atBlock"},Ie.keyframes=function(E,ee,K){return E=="word"?(Ce="variable","keyframes"):E=="{"?Pe(K,ee,"top"):Ue(E,ee,K)},Ie.at=function(E,ee,K){return E==";"?_e(K):E=="{"||E=="}"?et(E,ee,K):(E=="word"?Ce="tag":E=="hash"&&(Ce="builtin"),"at")},Ie.interpolation=function(E,ee,K){return E=="}"?_e(K):E=="{"||E==";"?et(E,ee,K):(E=="word"?Ce="variable":E!="variable"&&E!="("&&E!=")"&&(Ce="error"),"interpolation")},{startState:function(E){return{tokenize:null,state:$?"block":"top",stateArg:null,context:new dt($?"block":"top",E||0,null)}},token:function(E,ee){if(!ee.tokenize&&E.eatSpace())return null;var K=(ee.tokenize||Oe)(E,ee);return K&&typeof K=="object"&&(Fe=K[1],K=K[0]),Ce=K,Fe!="comment"&&(ee.state=Ie[ee.state](Fe,E,ee)),Ce},indent:function(E,ee){var K=E.context,ze=ee&&ee.charAt(0),me=K.indent;return K.type=="prop"&&(ze=="}"||ze==")")&&(K=K.prev),K.prev&&(ze=="}"&&(K.type=="block"||K.type=="top"||K.type=="interpolation"||K.type=="restricted_atBlock")?(K=K.prev,me=K.indent):(ze==")"&&(K.type=="parens"||K.type=="atBlock_parens")||ze=="{"&&(K.type=="at"||K.type=="atBlock"))&&(me=Math.max(0,K.indent-F))),me},electricChars:"}",blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:le,fold:"brace"}});function pe(J){for(var P={},$=0;$")):null:c.match("--")?C(ue("comment","-->")):c.match("DOCTYPE",!0,!0)?(c.eatWhile(/[\w\._\-]/),C(O(1))):null:c.eat("?")?(c.eatWhile(/[\w\._\-]/),T.tokenize=ue("meta","?>"),"meta"):(ne=c.eat("/")?"closeTag":"openTag",T.tokenize=A,"tag bracket");if(g=="&"){var y;return c.eat("#")?c.eat("x")?y=c.eatWhile(/[a-fA-F\d]/)&&c.eat(";"):y=c.eatWhile(/[\d]/)&&c.eat(";"):y=c.eatWhile(/[\w\.\-:]/)&&c.eat(";"),y?"atom":"error"}else return c.eatWhile(/[^&<]/),null}R.isInText=!0;function A(c,T){var C=c.next();if(C==">"||C=="/"&&c.eat(">"))return T.tokenize=R,ne=C==">"?"endTag":"selfcloseTag","tag bracket";if(C=="=")return ne="equals",null;if(C=="<"){T.tokenize=R,T.state=X,T.tagName=T.tagStart=null;var g=T.tokenize(c,T);return g?g+" tag error":"tag error"}else return/[\'\"]/.test(C)?(T.tokenize=V(C),T.stringStartCol=c.column(),T.tokenize(c,T)):(c.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function V(c){var T=function(C,g){for(;!C.eol();)if(C.next()==c){g.tokenize=A;break}return"string"};return T.isInAttribute=!0,T}function ue(c,T){return function(C,g){for(;!C.eol();){if(C.match(T)){g.tokenize=R;break}C.next()}return c}}function O(c){return function(T,C){for(var g;(g=T.next())!=null;){if(g=="<")return C.tokenize=O(c+1),C.tokenize(T,C);if(g==">")if(c==1){C.tokenize=R;break}else return C.tokenize=O(c-1),C.tokenize(T,C)}return"meta"}}function w(c){return c&&c.toLowerCase()}function M(c,T,C){this.prev=c.context,this.tagName=T||"",this.indent=c.indented,this.startOfLine=C,(k.doNotIndent.hasOwnProperty(T)||c.context&&c.context.noIndent)&&(this.noIndent=!0)}function N(c){c.context&&(c.context=c.context.prev)}function z(c,T){for(var C;;){if(!c.context||(C=c.context.tagName,!k.contextGrabbers.hasOwnProperty(w(C))||!k.contextGrabbers[w(C)].hasOwnProperty(w(T))))return;N(c)}}function X(c,T,C){return c=="openTag"?(C.tagStart=T.column(),q):c=="closeTag"?p:X}function q(c,T,C){return c=="word"?(C.tagName=T.current(),S="tag",P):k.allowMissingTagName&&c=="endTag"?(S="tag bracket",P(c,T,C)):(S="error",q)}function p(c,T,C){if(c=="word"){var g=T.current();return C.context&&C.context.tagName!=g&&k.implicitlyClosed.hasOwnProperty(w(C.context.tagName))&&N(C),C.context&&C.context.tagName==g||k.matchClosing===!1?(S="tag",W):(S="tag error",J)}else return k.allowMissingTagName&&c=="endTag"?(S="tag bracket",W(c,T,C)):(S="error",J)}function W(c,T,C){return c!="endTag"?(S="error",W):(N(C),X)}function J(c,T,C){return S="error",W(c,T,C)}function P(c,T,C){if(c=="word")return S="attribute",$;if(c=="endTag"||c=="selfcloseTag"){var g=C.tagName,y=C.tagStart;return C.tagName=C.tagStart=null,c=="selfcloseTag"||k.autoSelfClosers.hasOwnProperty(w(g))?z(C,g):(z(C,g),C.context=new M(C,g,y==C.indented)),X}return S="error",P}function $(c,T,C){return c=="equals"?F:(k.allowMissing||(S="error"),P(c,T,C))}function F(c,T,C){return c=="string"?G:c=="word"&&k.allowUnquoted?(S="string",P):(S="error",P(c,T,C))}function G(c,T,C){return c=="string"?G:P(c,T,C)}return{startState:function(c){var T={tokenize:R,state:X,indented:c||0,tagName:null,tagStart:null,context:null};return c!=null&&(T.baseIndent=c),T},token:function(c,T){if(!T.tagName&&c.sol()&&(T.indented=c.indentation()),c.eatSpace())return null;ne=null;var C=T.tokenize(c,T);return(C||ne)&&C!="comment"&&(S=null,T.state=T.state(ne||C,c,T),S&&(C=S=="error"?C+" error":S)),C},indent:function(c,T,C){var g=c.context;if(c.tokenize.isInAttribute)return c.tagStart==c.indented?c.stringStartCol+1:c.indented+Q;if(g&&g.noIndent)return b.Pass;if(c.tokenize!=A&&c.tokenize!=R)return C?C.match(/^(\s*)/)[0].length:0;if(c.tagName)return k.multilineTagIndentPastTag!==!1?c.tagStart+c.tagName.length+2:c.tagStart+Q*(k.multilineTagIndentFactor||1);if(k.alignCDATA&&/$/,blockCommentStart:"",configuration:k.htmlMode?"html":"xml",helperType:k.htmlMode?"html":"xml",skipAttribute:function(c){c.state==F&&(c.state=P)},xmlCurrentTag:function(c){return c.tagName?{name:c.tagName,close:c.type=="closeTag"}:null},xmlCurrentContext:function(c){for(var T=[],C=c.context;C;C=C.prev)T.push(C.tagName);return T.reverse()}}}),b.defineMIME("text/xml","xml"),b.defineMIME("application/xml","xml"),b.mimeModes.hasOwnProperty("text/html")||b.defineMIME("text/html",{name:"xml",htmlMode:!0})})})()),xa.exports}var ba={exports:{}},ka;function Qa(){return ka||(ka=1,(function(ct,xt){(function(b){b(mt())})(function(b){b.defineMode("javascript",function(pe,_){var te=pe.indentUnit,oe=_.statementIndent,Q=_.jsonld,k=_.json||Q,I=_.trackScope!==!1,Y=_.typescript,ne=_.wordCharacters||/[\w$\xa1-\uffff]/,S=(function(){function f(it){return{type:it,style:"keyword"}}var m=f("keyword a"),U=f("keyword b"),re=f("keyword c"),B=f("keyword d"),ce=f("operator"),We={type:"atom",style:"atom"};return{if:f("if"),while:m,with:m,else:U,do:U,try:U,finally:U,return:B,break:B,continue:B,new:f("new"),delete:re,void:re,throw:re,debugger:f("debugger"),var:f("var"),const:f("var"),let:f("var"),function:f("function"),catch:f("catch"),for:f("for"),switch:f("switch"),case:f("case"),default:f("default"),in:ce,typeof:ce,instanceof:ce,true:We,false:We,null:We,undefined:We,NaN:We,Infinity:We,this:f("this"),class:f("class"),super:f("atom"),yield:re,export:f("export"),import:f("import"),extends:re,await:re}})(),R=/[+\-*&%=<>!?|~^@]/,A=/^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;function V(f){for(var m=!1,U,re=!1;(U=f.next())!=null;){if(!m){if(U=="/"&&!re)return;U=="["?re=!0:re&&U=="]"&&(re=!1)}m=!m&&U=="\\"}}var ue,O;function w(f,m,U){return ue=f,O=U,m}function M(f,m){var U=f.next();if(U=='"'||U=="'")return m.tokenize=N(U),m.tokenize(f,m);if(U=="."&&f.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/))return w("number","number");if(U=="."&&f.match(".."))return w("spread","meta");if(/[\[\]{}\(\),;\:\.]/.test(U))return w(U);if(U=="="&&f.eat(">"))return w("=>","operator");if(U=="0"&&f.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/))return w("number","number");if(/\d/.test(U))return f.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/),w("number","number");if(U=="/")return f.eat("*")?(m.tokenize=z,z(f,m)):f.eat("/")?(f.skipToEnd(),w("comment","comment")):Et(f,m,1)?(V(f),f.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/),w("regexp","string-2")):(f.eat("="),w("operator","operator",f.current()));if(U=="`")return m.tokenize=X,X(f,m);if(U=="#"&&f.peek()=="!")return f.skipToEnd(),w("meta","meta");if(U=="#"&&f.eatWhile(ne))return w("variable","property");if(U=="<"&&f.match("!--")||U=="-"&&f.match("->")&&!/\S/.test(f.string.slice(0,f.start)))return f.skipToEnd(),w("comment","comment");if(R.test(U))return(U!=">"||!m.lexical||m.lexical.type!=">")&&(f.eat("=")?(U=="!"||U=="=")&&f.eat("="):/[<>*+\-|&?]/.test(U)&&(f.eat(U),U==">"&&f.eat(U))),U=="?"&&f.eat(".")?w("."):w("operator","operator",f.current());if(ne.test(U)){f.eatWhile(ne);var re=f.current();if(m.lastType!="."){if(S.propertyIsEnumerable(re)){var B=S[re];return w(B.type,B.style,re)}if(re=="async"&&f.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/,!1))return w("async","keyword",re)}return w("variable","variable",re)}}function N(f){return function(m,U){var re=!1,B;if(Q&&m.peek()=="@"&&m.match(A))return U.tokenize=M,w("jsonld-keyword","meta");for(;(B=m.next())!=null&&!(B==f&&!re);)re=!re&&B=="\\";return re||(U.tokenize=M),w("string","string")}}function z(f,m){for(var U=!1,re;re=f.next();){if(re=="/"&&U){m.tokenize=M;break}U=re=="*"}return w("comment","comment")}function X(f,m){for(var U=!1,re;(re=f.next())!=null;){if(!U&&(re=="`"||re=="$"&&f.eat("{"))){m.tokenize=M;break}U=!U&&re=="\\"}return w("quasi","string-2",f.current())}var q="([{}])";function p(f,m){m.fatArrowAt&&(m.fatArrowAt=null);var U=f.string.indexOf("=>",f.start);if(!(U<0)){if(Y){var re=/:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(f.string.slice(f.start,U));re&&(U=re.index)}for(var B=0,ce=!1,We=U-1;We>=0;--We){var it=f.string.charAt(We),wt=q.indexOf(it);if(wt>=0&&wt<3){if(!B){++We;break}if(--B==0){it=="("&&(ce=!0);break}}else if(wt>=3&&wt<6)++B;else if(ne.test(it))ce=!0;else if(/["'\/`]/.test(it))for(;;--We){if(We==0)return;var Wr=f.string.charAt(We-1);if(Wr==it&&f.string.charAt(We-2)!="\\"){We--;break}}else if(ce&&!B){++We;break}}ce&&!B&&(m.fatArrowAt=We)}}var W={atom:!0,number:!0,variable:!0,string:!0,regexp:!0,this:!0,import:!0,"jsonld-keyword":!0};function J(f,m,U,re,B,ce){this.indented=f,this.column=m,this.type=U,this.prev=B,this.info=ce,re!=null&&(this.align=re)}function P(f,m){if(!I)return!1;for(var U=f.localVars;U;U=U.next)if(U.name==m)return!0;for(var re=f.context;re;re=re.prev)for(var U=re.vars;U;U=U.next)if(U.name==m)return!0}function $(f,m,U,re,B){var ce=f.cc;for(F.state=f,F.stream=B,F.marked=null,F.cc=ce,F.style=m,f.lexical.hasOwnProperty("align")||(f.lexical.align=!0);;){var We=ce.length?ce.pop():k?ve:Fe;if(We(U,re)){for(;ce.length&&ce[ce.length-1].lex;)ce.pop()();return F.marked?F.marked:U=="variable"&&P(f,re)?"variable-2":m}}}var F={state:null,marked:null,cc:null};function G(){for(var f=arguments.length-1;f>=0;f--)F.cc.push(arguments[f])}function c(){return G.apply(null,arguments),!0}function T(f,m){for(var U=m;U;U=U.next)if(U.name==f)return!0;return!1}function C(f){var m=F.state;if(F.marked="def",!!I){if(m.context){if(m.lexical.info=="var"&&m.context&&m.context.block){var U=g(f,m.context);if(U!=null){m.context=U;return}}else if(!T(f,m.localVars)){m.localVars=new de(f,m.localVars);return}}_.globalVars&&!T(f,m.globalVars)&&(m.globalVars=new de(f,m.globalVars))}}function g(f,m){if(m)if(m.block){var U=g(f,m.prev);return U?U==m.prev?m:new j(U,m.vars,!0):null}else return T(f,m.vars)?m:new j(m.prev,new de(f,m.vars),!1);else return null}function y(f){return f=="public"||f=="private"||f=="protected"||f=="abstract"||f=="readonly"}function j(f,m,U){this.prev=f,this.vars=m,this.block=U}function de(f,m){this.name=f,this.next=m}var v=new de("this",new de("arguments",null));function d(){F.state.context=new j(F.state.context,F.state.localVars,!1),F.state.localVars=v}function fe(){F.state.context=new j(F.state.context,F.state.localVars,!0),F.state.localVars=null}d.lex=fe.lex=!0;function Te(){F.state.localVars=F.state.context.vars,F.state.context=F.state.context.prev}Te.lex=!0;function le(f,m){var U=function(){var re=F.state,B=re.indented;if(re.lexical.type=="stat")B=re.lexical.indented;else for(var ce=re.lexical;ce&&ce.type==")"&&ce.align;ce=ce.prev)B=ce.indented;re.lexical=new J(B,F.stream.column(),f,null,re.lexical,m)};return U.lex=!0,U}function xe(){var f=F.state;f.lexical.prev&&(f.lexical.type==")"&&(f.indented=f.lexical.indented),f.lexical=f.lexical.prev)}xe.lex=!0;function Me(f){function m(U){return U==f?c():f==";"||U=="}"||U==")"||U=="]"?G():c(m)}return m}function Fe(f,m){return f=="var"?c(le("vardef",m),Er,Me(";"),xe):f=="keyword a"?c(le("form"),qe,Fe,xe):f=="keyword b"?c(le("form"),Fe,xe):f=="keyword d"?F.stream.match(/^\s*$/,!1)?c():c(le("stat"),dt,Me(";"),xe):f=="debugger"?c(Me(";")):f=="{"?c(le("}"),fe,Pt,xe,Te):f==";"?c():f=="if"?(F.state.lexical.info=="else"&&F.state.cc[F.state.cc.length-1]==xe&&F.state.cc.pop()(),c(le("form"),qe,Fe,xe,Or)):f=="function"?c(zt):f=="for"?c(le("form"),fe,Rn,Fe,Te,xe):f=="class"||Y&&m=="interface"?(F.marked="keyword",c(le("form",f=="class"?f:m),Pr,xe)):f=="variable"?Y&&m=="declare"?(F.marked="keyword",c(Fe)):Y&&(m=="module"||m=="enum"||m=="type")&&F.stream.match(/^\s*\w/,!1)?(F.marked="keyword",m=="enum"?c(ye):m=="type"?c(Wn,Me("operator"),Re,Me(";")):c(le("form"),kt,Me("{"),le("}"),Pt,xe,xe)):Y&&m=="namespace"?(F.marked="keyword",c(le("form"),ve,Fe,xe)):Y&&m=="abstract"?(F.marked="keyword",c(Fe)):c(le("stat"),ze):f=="switch"?c(le("form"),qe,Me("{"),le("}","switch"),fe,Pt,xe,xe,Te):f=="case"?c(ve,Me(":")):f=="default"?c(Me(":")):f=="catch"?c(le("form"),d,Ce,Fe,xe,Te):f=="export"?c(le("stat"),Ir,xe):f=="import"?c(le("stat"),fr,xe):f=="async"?c(Fe):m=="@"?c(ve,Fe):G(le("stat"),ve,Me(";"),xe)}function Ce(f){if(f=="(")return c(Wt,Me(")"))}function ve(f,m){return Ve(f,m,!1)}function Oe(f,m){return Ve(f,m,!0)}function qe(f){return f!="("?G():c(le(")"),dt,Me(")"),xe)}function Ve(f,m,U){if(F.state.fatArrowAt==F.stream.start){var re=U?Ie:we;if(f=="(")return c(d,le(")"),Ne(Wt,")"),xe,Me("=>"),re,Te);if(f=="variable")return G(d,kt,Me("=>"),re,Te)}var B=U?_e:Pe;return W.hasOwnProperty(f)?c(B):f=="function"?c(zt,B):f=="class"||Y&&m=="interface"?(F.marked="keyword",c(le("form"),yi,xe)):f=="keyword c"||f=="async"?c(U?Oe:ve):f=="("?c(le(")"),dt,Me(")"),xe,B):f=="operator"||f=="spread"?c(U?Oe:ve):f=="["?c(le("]"),Je,xe,B):f=="{"?Mt(De,"}",null,B):f=="quasi"?G(Ue,B):f=="new"?c(E(U)):c()}function dt(f){return f.match(/[;\}\)\],]/)?G():G(ve)}function Pe(f,m){return f==","?c(dt):_e(f,m,!1)}function _e(f,m,U){var re=U==!1?Pe:_e,B=U==!1?ve:Oe;if(f=="=>")return c(d,U?Ie:we,Te);if(f=="operator")return/\+\+|--/.test(m)||Y&&m=="!"?c(re):Y&&m=="<"&&F.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/,!1)?c(le(">"),Ne(Re,">"),xe,re):m=="?"?c(ve,Me(":"),B):c(B);if(f=="quasi")return G(Ue,re);if(f!=";"){if(f=="(")return Mt(Oe,")","call",re);if(f==".")return c(me,re);if(f=="[")return c(le("]"),dt,Me("]"),xe,re);if(Y&&m=="as")return F.marked="keyword",c(Re,re);if(f=="regexp")return F.state.lastType=F.marked="operator",F.stream.backUp(F.stream.pos-F.stream.start-1),c(B)}}function Ue(f,m){return f!="quasi"?G():m.slice(m.length-2)!="${"?c(Ue):c(dt,et)}function et(f){if(f=="}")return F.marked="string-2",F.state.tokenize=X,c(Ue)}function we(f){return p(F.stream,F.state),G(f=="{"?Fe:ve)}function Ie(f){return p(F.stream,F.state),G(f=="{"?Fe:Oe)}function E(f){return function(m){return m=="."?c(f?K:ee):m=="variable"&&Y?c(Ft,f?_e:Pe):G(f?Oe:ve)}}function ee(f,m){if(m=="target")return F.marked="keyword",c(Pe)}function K(f,m){if(m=="target")return F.marked="keyword",c(_e)}function ze(f){return f==":"?c(xe,Fe):G(Pe,Me(";"),xe)}function me(f){if(f=="variable")return F.marked="property",c()}function De(f,m){if(f=="async")return F.marked="property",c(De);if(f=="variable"||F.style=="keyword"){if(F.marked="property",m=="get"||m=="set")return c(be);var U;return Y&&F.state.fatArrowAt==F.stream.start&&(U=F.stream.match(/^\s*:\s*/,!1))&&(F.state.fatArrowAt=F.stream.pos+U[0].length),c(Be)}else{if(f=="number"||f=="string")return F.marked=Q?"property":F.style+" property",c(Be);if(f=="jsonld-keyword")return c(Be);if(Y&&y(m))return F.marked="keyword",c(De);if(f=="[")return c(ve,or,Me("]"),Be);if(f=="spread")return c(Oe,Be);if(m=="*")return F.marked="keyword",c(De);if(f==":")return G(Be)}}function be(f){return f!="variable"?G(Be):(F.marked="property",c(zt))}function Be(f){if(f==":")return c(Oe);if(f=="(")return G(zt)}function Ne(f,m,U){function re(B,ce){if(U?U.indexOf(B)>-1:B==","){var We=F.state.lexical;return We.info=="call"&&(We.pos=(We.pos||0)+1),c(function(it,wt){return it==m||wt==m?G():G(f)},re)}return B==m||ce==m?c():U&&U.indexOf(";")>-1?G(f):c(Me(m))}return function(B,ce){return B==m||ce==m?c():G(f,re)}}function Mt(f,m,U){for(var re=3;re"),Re);if(f=="quasi")return G(ht,It)}function Bn(f){if(f=="=>")return c(Re)}function Se(f){return f.match(/[\}\)\]]/)?c():f==","||f==";"?c(Se):G(Zt,Se)}function Zt(f,m){if(f=="variable"||F.style=="keyword")return F.marked="property",c(Zt);if(m=="?"||f=="number"||f=="string")return c(Zt);if(f==":")return c(Re);if(f=="[")return c(Me("variable"),br,Me("]"),Zt);if(f=="(")return G(ur,Zt);if(!f.match(/[;\}\)\],]/))return c()}function ht(f,m){return f!="quasi"?G():m.slice(m.length-2)!="${"?c(ht):c(Re,Ye)}function Ye(f){if(f=="}")return F.marked="string-2",F.state.tokenize=X,c(ht)}function Qe(f,m){return f=="variable"&&F.stream.match(/^\s*[?:]/,!1)||m=="?"?c(Qe):f==":"?c(Re):f=="spread"?c(Qe):G(Re)}function It(f,m){if(m=="<")return c(le(">"),Ne(Re,">"),xe,It);if(m=="|"||f=="."||m=="&")return c(Re);if(f=="[")return c(Re,Me("]"),It);if(m=="extends"||m=="implements")return F.marked="keyword",c(Re);if(m=="?")return c(Re,Me(":"),Re)}function Ft(f,m){if(m=="<")return c(le(">"),Ne(Re,">"),xe,It)}function Bt(){return G(Re,pt)}function pt(f,m){if(m=="=")return c(Re)}function Er(f,m){return m=="enum"?(F.marked="keyword",c(ye)):G(kt,or,Rt,xi)}function kt(f,m){if(Y&&y(m))return F.marked="keyword",c(kt);if(f=="variable")return C(m),c();if(f=="spread")return c(kt);if(f=="[")return Mt(ln,"]");if(f=="{")return Mt(ar,"}")}function ar(f,m){return f=="variable"&&!F.stream.match(/^\s*:/,!1)?(C(m),c(Rt)):(f=="variable"&&(F.marked="property"),f=="spread"?c(kt):f=="}"?G():f=="["?c(ve,Me("]"),Me(":"),ar):c(Me(":"),kt,Rt))}function ln(){return G(kt,Rt)}function Rt(f,m){if(m=="=")return c(Oe)}function xi(f){if(f==",")return c(Er)}function Or(f,m){if(f=="keyword b"&&m=="else")return c(le("form","else"),Fe,xe)}function Rn(f,m){if(m=="await")return c(Rn);if(f=="(")return c(le(")"),an,xe)}function an(f){return f=="var"?c(Er,sr):f=="variable"?c(sr):G(sr)}function sr(f,m){return f==")"?c():f==";"?c(sr):m=="in"||m=="of"?(F.marked="keyword",c(ve,sr)):G(ve,sr)}function zt(f,m){if(m=="*")return F.marked="keyword",c(zt);if(f=="variable")return C(m),c(zt);if(f=="(")return c(d,le(")"),Ne(Wt,")"),xe,lr,Fe,Te);if(Y&&m=="<")return c(le(">"),Ne(Bt,">"),xe,zt)}function ur(f,m){if(m=="*")return F.marked="keyword",c(ur);if(f=="variable")return C(m),c(ur);if(f=="(")return c(d,le(")"),Ne(Wt,")"),xe,lr,Te);if(Y&&m=="<")return c(le(">"),Ne(Bt,">"),xe,ur)}function Wn(f,m){if(f=="keyword"||f=="variable")return F.marked="type",c(Wn);if(m=="<")return c(le(">"),Ne(Bt,">"),xe)}function Wt(f,m){return m=="@"&&c(ve,Wt),f=="spread"?c(Wt):Y&&y(m)?(F.marked="keyword",c(Wt)):Y&&f=="this"?c(or,Rt):G(kt,or,Rt)}function yi(f,m){return f=="variable"?Pr(f,m):Ht(f,m)}function Pr(f,m){if(f=="variable")return C(m),c(Ht)}function Ht(f,m){if(m=="<")return c(le(">"),Ne(Bt,">"),xe,Ht);if(m=="extends"||m=="implements"||Y&&f==",")return m=="implements"&&(F.marked="keyword"),c(Y?Re:ve,Ht);if(f=="{")return c(le("}"),_t,xe)}function _t(f,m){if(f=="async"||f=="variable"&&(m=="static"||m=="get"||m=="set"||Y&&y(m))&&F.stream.match(/^\s+#?[\w$\xa1-\uffff]/,!1))return F.marked="keyword",c(_t);if(f=="variable"||F.style=="keyword")return F.marked="property",c(kr,_t);if(f=="number"||f=="string")return c(kr,_t);if(f=="[")return c(ve,or,Me("]"),kr,_t);if(m=="*")return F.marked="keyword",c(_t);if(Y&&f=="(")return G(ur,_t);if(f==";"||f==",")return c(_t);if(f=="}")return c();if(m=="@")return c(ve,_t)}function kr(f,m){if(m=="!"||m=="?")return c(kr);if(f==":")return c(Re,Rt);if(m=="=")return c(Oe);var U=F.state.lexical.prev,re=U&&U.info=="interface";return G(re?ur:zt)}function Ir(f,m){return m=="*"?(F.marked="keyword",c(Rr,Me(";"))):m=="default"?(F.marked="keyword",c(ve,Me(";"))):f=="{"?c(Ne(zr,"}"),Rr,Me(";")):G(Fe)}function zr(f,m){if(m=="as")return F.marked="keyword",c(Me("variable"));if(f=="variable")return G(Oe,zr)}function fr(f){return f=="string"?c():f=="("?G(ve):f=="."?G(Pe):G(Br,Gt,Rr)}function Br(f,m){return f=="{"?Mt(Br,"}"):(f=="variable"&&C(m),m=="*"&&(F.marked="keyword"),c(sn))}function Gt(f){if(f==",")return c(Br,Gt)}function sn(f,m){if(m=="as")return F.marked="keyword",c(Br)}function Rr(f,m){if(m=="from")return F.marked="keyword",c(ve)}function Je(f){return f=="]"?c():G(Ne(Oe,"]"))}function ye(){return G(le("form"),kt,Me("{"),le("}"),Ne(Vt,"}"),xe,xe)}function Vt(){return G(kt,Rt)}function un(f,m){return f.lastType=="operator"||f.lastType==","||R.test(m.charAt(0))||/[,.]/.test(m.charAt(0))}function Et(f,m,U){return m.tokenize==M&&/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(m.lastType)||m.lastType=="quasi"&&/\{\s*$/.test(f.string.slice(0,f.pos-(U||0)))}return{startState:function(f){var m={tokenize:M,lastType:"sof",cc:[],lexical:new J((f||0)-te,0,"block",!1),localVars:_.localVars,context:_.localVars&&new j(null,null,!1),indented:f||0};return _.globalVars&&typeof _.globalVars=="object"&&(m.globalVars=_.globalVars),m},token:function(f,m){if(f.sol()&&(m.lexical.hasOwnProperty("align")||(m.lexical.align=!1),m.indented=f.indentation(),p(f,m)),m.tokenize!=z&&f.eatSpace())return null;var U=m.tokenize(f,m);return ue=="comment"?U:(m.lastType=ue=="operator"&&(O=="++"||O=="--")?"incdec":ue,$(m,U,ue,O,f))},indent:function(f,m){if(f.tokenize==z||f.tokenize==X)return b.Pass;if(f.tokenize!=M)return 0;var U=m&&m.charAt(0),re=f.lexical,B;if(!/^\s*else\b/.test(m))for(var ce=f.cc.length-1;ce>=0;--ce){var We=f.cc[ce];if(We==xe)re=re.prev;else if(We!=Or&&We!=Te)break}for(;(re.type=="stat"||re.type=="form")&&(U=="}"||(B=f.cc[f.cc.length-1])&&(B==Pe||B==_e)&&!/^[,\.=+\-*:?[\(]/.test(m));)re=re.prev;oe&&re.type==")"&&re.prev.type=="stat"&&(re=re.prev);var it=re.type,wt=U==it;return it=="vardef"?re.indented+(f.lastType=="operator"||f.lastType==","?re.info.length+1:0):it=="form"&&U=="{"?re.indented:it=="form"?re.indented+te:it=="stat"?re.indented+(un(f,m)?oe||te:0):re.info=="switch"&&!wt&&_.doubleIndentSwitch!=!1?re.indented+(/^(?:case|default)\b/.test(m)?te:2*te):re.align?re.column+(wt?0:1):re.indented+(wt?0:te)},electricInput:/^\s*(?:case .*?:|default:|\{|\})$/,blockCommentStart:k?null:"/*",blockCommentEnd:k?null:"*/",blockCommentContinue:k?null:" * ",lineComment:k?null:"//",fold:"brace",closeBrackets:"()[]{}''\"\"``",helperType:k?"json":"javascript",jsonldMode:Q,jsonMode:k,expressionAllowed:Et,skipExpression:function(f){$(f,"atom","atom","true",new b.StringStream("",2,null))}}}),b.registerHelper("wordChars","javascript",/[\w$]/),b.defineMIME("text/javascript","javascript"),b.defineMIME("text/ecmascript","javascript"),b.defineMIME("application/javascript","javascript"),b.defineMIME("application/x-javascript","javascript"),b.defineMIME("application/ecmascript","javascript"),b.defineMIME("application/json",{name:"javascript",json:!0}),b.defineMIME("application/x-json",{name:"javascript",json:!0}),b.defineMIME("application/manifest+json",{name:"javascript",json:!0}),b.defineMIME("application/ld+json",{name:"javascript",jsonld:!0}),b.defineMIME("text/typescript",{name:"javascript",typescript:!0}),b.defineMIME("application/typescript",{name:"javascript",typescript:!0})})})()),ba.exports}var wa;function $u(){return wa||(wa=1,(function(ct,xt){(function(b){b(mt(),Ya(),Qa(),Xa())})(function(b){var pe={script:[["lang",/(javascript|babel)/i,"javascript"],["type",/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i,"javascript"],["type",/./,"text/plain"],[null,null,"javascript"]],style:[["lang",/^css$/i,"css"],["type",/^(text\/)?(x-)?(stylesheet|css)$/i,"css"],["type",/./,"text/plain"],[null,null,"css"]]};function _(ne,S,R){var A=ne.current(),V=A.search(S);return V>-1?ne.backUp(A.length-V):A.match(/<\/?$/)&&(ne.backUp(A.length),ne.match(S,!1)||ne.match(A)),R}var te={};function oe(ne){var S=te[ne];return S||(te[ne]=new RegExp("\\s+"+ne+`\\s*=\\s*('|")?([^'"]+)('|")?\\s*`))}function Q(ne,S){var R=ne.match(oe(S));return R?/^\s*(.*?)\s*$/.exec(R[2])[1]:""}function k(ne,S){return new RegExp((S?"^":"")+"","i")}function I(ne,S){for(var R in ne)for(var A=S[R]||(S[R]=[]),V=ne[R],ue=V.length-1;ue>=0;ue--)A.unshift(V[ue])}function Y(ne,S){for(var R=0;R=0;O--)A.script.unshift(["type",ue[O].matches,ue[O].mode]);function w(M,N){var z=R.token(M,N.htmlState),X=/\btag\b/.test(z),q;if(X&&!/[<>\s\/]/.test(M.current())&&(q=N.htmlState.tagName&&N.htmlState.tagName.toLowerCase())&&A.hasOwnProperty(q))N.inTag=q+" ";else if(N.inTag&&X&&/>$/.test(M.current())){var p=/^([\S]+) (.*)/.exec(N.inTag);N.inTag=null;var W=M.current()==">"&&Y(A[p[1]],p[2]),J=b.getMode(ne,W),P=k(p[1],!0),$=k(p[1],!1);N.token=function(F,G){return F.match(P,!1)?(G.token=w,G.localState=G.localMode=null,null):_(F,$,G.localMode.token(F,G.localState))},N.localMode=J,N.localState=b.startState(J,R.indent(N.htmlState,"",""))}else N.inTag&&(N.inTag+=M.current(),M.eol()&&(N.inTag+=" "));return z}return{startState:function(){var M=b.startState(R);return{token:w,inTag:null,localMode:null,localState:null,htmlState:M}},copyState:function(M){var N;return M.localState&&(N=b.copyState(M.localMode,M.localState)),{token:M.token,inTag:M.inTag,localMode:M.localMode,localState:N,htmlState:b.copyState(R,M.htmlState)}},token:function(M,N){return N.token(M,N)},indent:function(M,N,z){return!M.localMode||/^\s*<\//.test(N)?R.indent(M.htmlState,N,z):M.localMode.indent?M.localMode.indent(M.localState,N,z):b.Pass},innerMode:function(M){return{state:M.localState||M.htmlState,mode:M.localMode||R}}}},"xml","javascript","css"),b.defineMIME("text/html","htmlmixed")})})()),ma.exports}$u();Qa();var Sa={exports:{}},La;function ef(){return La||(La=1,(function(ct,xt){(function(b){b(mt())})(function(b){function pe(I){return new RegExp("^(("+I.join(")|(")+"))\\b")}var _=pe(["and","or","not","is"]),te=["as","assert","break","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","lambda","pass","raise","return","try","while","with","yield","in","False","True"],oe=["abs","all","any","bin","bool","bytearray","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip","__import__","NotImplemented","Ellipsis","__debug__"];b.registerHelper("hintWords","python",te.concat(oe).concat(["exec","print"]));function Q(I){return I.scopes[I.scopes.length-1]}b.defineMode("python",function(I,Y){for(var ne="error",S=Y.delimiters||Y.singleDelimiters||/^[\(\)\[\]\{\}@,:`=;\.\\]/,R=[Y.singleOperators,Y.doubleOperators,Y.doubleDelimiters,Y.tripleDelimiters,Y.operators||/^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/],A=0;Ay?P(C):j0&&F(T,C)&&(de+=" "+ne),de}}return p(T,C)}function p(T,C,g){if(T.eatSpace())return null;if(!g&&T.match(/^#.*/))return"comment";if(T.match(/^[0-9\.]/,!1)){var y=!1;if(T.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)&&(y=!0),T.match(/^[\d_]+\.\d*/)&&(y=!0),T.match(/^\.\d+/)&&(y=!0),y)return T.eat(/J/i),"number";var j=!1;if(T.match(/^0x[0-9a-f_]+/i)&&(j=!0),T.match(/^0b[01_]+/i)&&(j=!0),T.match(/^0o[0-7_]+/i)&&(j=!0),T.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)&&(T.eat(/J/i),j=!0),T.match(/^0(?![\dx])/i)&&(j=!0),j)return T.eat(/L/i),"number"}if(T.match(N)){var de=T.current().toLowerCase().indexOf("f")!==-1;return de?(C.tokenize=W(T.current(),C.tokenize),C.tokenize(T,C)):(C.tokenize=J(T.current(),C.tokenize),C.tokenize(T,C))}for(var v=0;v=0;)T=T.substr(1);var g=T.length==1,y="string";function j(v){return function(d,fe){var Te=p(d,fe,!0);return Te=="punctuation"&&(d.current()=="{"?fe.tokenize=j(v+1):d.current()=="}"&&(v>1?fe.tokenize=j(v-1):fe.tokenize=de)),Te}}function de(v,d){for(;!v.eol();)if(v.eatWhile(/[^'"\{\}\\]/),v.eat("\\")){if(v.next(),g&&v.eol())return y}else{if(v.match(T))return d.tokenize=C,y;if(v.match("{{"))return y;if(v.match("{",!1))return d.tokenize=j(0),v.current()?y:d.tokenize(v,d);if(v.match("}}"))return y;if(v.match("}"))return ne;v.eat(/['"]/)}if(g){if(Y.singleLineStringErrors)return ne;d.tokenize=C}return y}return de.isString=!0,de}function J(T,C){for(;"rubf".indexOf(T.charAt(0).toLowerCase())>=0;)T=T.substr(1);var g=T.length==1,y="string";function j(de,v){for(;!de.eol();)if(de.eatWhile(/[^'"\\]/),de.eat("\\")){if(de.next(),g&&de.eol())return y}else{if(de.match(T))return v.tokenize=C,y;de.eat(/['"]/)}if(g){if(Y.singleLineStringErrors)return ne;v.tokenize=C}return y}return j.isString=!0,j}function P(T){for(;Q(T).type!="py";)T.scopes.pop();T.scopes.push({offset:Q(T).offset+I.indentUnit,type:"py",align:null})}function $(T,C,g){var y=T.match(/^[\s\[\{\(]*(?:#|$)/,!1)?null:T.column()+1;C.scopes.push({offset:C.indent+V,type:g,align:y})}function F(T,C){for(var g=T.indentation();C.scopes.length>1&&Q(C).offset>g;){if(Q(C).type!="py")return!0;C.scopes.pop()}return Q(C).offset!=g}function G(T,C){T.sol()&&(C.beginningOfLine=!0,C.dedent=!1);var g=C.tokenize(T,C),y=T.current();if(C.beginningOfLine&&y=="@")return T.match(M,!1)?"meta":w?"operator":ne;if(/\S/.test(y)&&(C.beginningOfLine=!1),(g=="variable"||g=="builtin")&&C.lastToken=="meta"&&(g="meta"),(y=="pass"||y=="return")&&(C.dedent=!0),y=="lambda"&&(C.lambda=!0),y==":"&&!C.lambda&&Q(C).type=="py"&&T.match(/^\s*(?:#|$)/,!1)&&P(C),y.length==1&&!/string|comment/.test(g)){var j="[({".indexOf(y);if(j!=-1&&$(T,C,"])}".slice(j,j+1)),j="])}".indexOf(y),j!=-1)if(Q(C).type==y)C.indent=C.scopes.pop().offset-V;else return ne}return C.dedent&&T.eol()&&Q(C).type=="py"&&C.scopes.length>1&&C.scopes.pop(),g}var c={startState:function(T){return{tokenize:q,scopes:[{offset:T||0,type:"py",align:null}],indent:T||0,lastToken:null,lambda:!1,dedent:0}},token:function(T,C){var g=C.errorToken;g&&(C.errorToken=!1);var y=G(T,C);return y&&y!="comment"&&(C.lastToken=y=="keyword"||y=="punctuation"?T.current():y),y=="punctuation"&&(y=null),T.eol()&&C.lambda&&(C.lambda=!1),g?y+" "+ne:y},indent:function(T,C){if(T.tokenize!=q)return T.tokenize.isString?b.Pass:0;var g=Q(T),y=g.type==C.charAt(0)||g.type=="py"&&!T.dedent&&/^(else:|elif |except |finally:)/.test(C);return g.align!=null?g.align-(y?1:0):g.offset-(y?V:0)},electricInput:/^\s*([\}\]\)]|else:|elif |except |finally:)$/,closeBrackets:{triples:`'"`},lineComment:"#",fold:"indent"};return c}),b.defineMIME("text/x-python","python");var k=function(I){return I.split(" ")};b.defineMIME("text/x-cython",{name:"python",extra_keywords:k("by cdef cimport cpdef ctypedef enum except extern gil include nogil property public readonly struct union DEF IF ELIF ELSE")})})})()),Sa.exports}ef();var Ta={exports:{}},Ca;function tf(){return Ca||(Ca=1,(function(ct,xt){(function(b){b(mt())})(function(b){function pe(g,y,j,de,v,d){this.indented=g,this.column=y,this.type=j,this.info=de,this.align=v,this.prev=d}function _(g,y,j,de){var v=g.indented;return g.context&&g.context.type=="statement"&&j!="statement"&&(v=g.context.indented),g.context=new pe(v,y,j,de,null,g.context)}function te(g){var y=g.context.type;return(y==")"||y=="]"||y=="}")&&(g.indented=g.context.indented),g.context=g.context.prev}function oe(g,y,j){if(y.prevToken=="variable"||y.prevToken=="type"||/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(g.string.slice(0,j))||y.typeAtEndOfLine&&g.column()==g.indentation())return!0}function Q(g){for(;;){if(!g||g.type=="top")return!0;if(g.type=="}"&&g.prev.info!="namespace")return!1;g=g.prev}}b.defineMode("clike",function(g,y){var j=g.indentUnit,de=y.statementIndentUnit||j,v=y.dontAlignCalls,d=y.keywords||{},fe=y.types||{},Te=y.builtin||{},le=y.blockKeywords||{},xe=y.defKeywords||{},Me=y.atoms||{},Fe=y.hooks||{},Ce=y.multiLineStrings,ve=y.indentStatements!==!1,Oe=y.indentSwitch!==!1,qe=y.namespaceSeparator,Ve=y.isPunctuationChar||/[\[\]{}\(\),;\:\.]/,dt=y.numberStart||/[\d\.]/,Pe=y.number||/^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,_e=y.isOperatorChar||/[+\-*&%=<>!?|\/]/,Ue=y.isIdentifierChar||/[\w\$_\xa1-\uffff]/,et=y.isReservedIdentifier||!1,we,Ie;function E(me,De){var be=me.next();if(Fe[be]){var Be=Fe[be](me,De);if(Be!==!1)return Be}if(be=='"'||be=="'")return De.tokenize=ee(be),De.tokenize(me,De);if(dt.test(be)){if(me.backUp(1),me.match(Pe))return"number";me.next()}if(Ve.test(be))return we=be,null;if(be=="/"){if(me.eat("*"))return De.tokenize=K,K(me,De);if(me.eat("/"))return me.skipToEnd(),"comment"}if(_e.test(be)){for(;!me.match(/^\/[\/*]/,!1)&&me.eat(_e););return"operator"}if(me.eatWhile(Ue),qe)for(;me.match(qe);)me.eatWhile(Ue);var Ne=me.current();return I(d,Ne)?(I(le,Ne)&&(we="newstatement"),I(xe,Ne)&&(Ie=!0),"keyword"):I(fe,Ne)?"type":I(Te,Ne)||et&&et(Ne)?(I(le,Ne)&&(we="newstatement"),"builtin"):I(Me,Ne)?"atom":"variable"}function ee(me){return function(De,be){for(var Be=!1,Ne,Mt=!1;(Ne=De.next())!=null;){if(Ne==me&&!Be){Mt=!0;break}Be=!Be&&Ne=="\\"}return(Mt||!(Be||Ce))&&(be.tokenize=null),"string"}}function K(me,De){for(var be=!1,Be;Be=me.next();){if(Be=="/"&&be){De.tokenize=null;break}be=Be=="*"}return"comment"}function ze(me,De){y.typeFirstDefinitions&&me.eol()&&Q(De.context)&&(De.typeAtEndOfLine=oe(me,De,me.pos))}return{startState:function(me){return{tokenize:null,context:new pe((me||0)-j,0,"top",null,!1),indented:0,startOfLine:!0,prevToken:null}},token:function(me,De){var be=De.context;if(me.sol()&&(be.align==null&&(be.align=!1),De.indented=me.indentation(),De.startOfLine=!0),me.eatSpace())return ze(me,De),null;we=Ie=null;var Be=(De.tokenize||E)(me,De);if(Be=="comment"||Be=="meta")return Be;if(be.align==null&&(be.align=!0),we==";"||we==":"||we==","&&me.match(/^\s*(?:\/\/.*)?$/,!1))for(;De.context.type=="statement";)te(De);else if(we=="{")_(De,me.column(),"}");else if(we=="[")_(De,me.column(),"]");else if(we=="(")_(De,me.column(),")");else if(we=="}"){for(;be.type=="statement";)be=te(De);for(be.type=="}"&&(be=te(De));be.type=="statement";)be=te(De)}else we==be.type?te(De):ve&&((be.type=="}"||be.type=="top")&&we!=";"||be.type=="statement"&&we=="newstatement")&&_(De,me.column(),"statement",me.current());if(Be=="variable"&&(De.prevToken=="def"||y.typeFirstDefinitions&&oe(me,De,me.start)&&Q(De.context)&&me.match(/^\s*\(/,!1))&&(Be="def"),Fe.token){var Ne=Fe.token(me,De,Be);Ne!==void 0&&(Be=Ne)}return Be=="def"&&y.styleDefs===!1&&(Be="variable"),De.startOfLine=!1,De.prevToken=Ie?"def":Be||we,ze(me,De),Be},indent:function(me,De){if(me.tokenize!=E&&me.tokenize!=null||me.typeAtEndOfLine&&Q(me.context))return b.Pass;var be=me.context,Be=De&&De.charAt(0),Ne=Be==be.type;if(be.type=="statement"&&Be=="}"&&(be=be.prev),y.dontIndentStatements)for(;be.type=="statement"&&y.dontIndentStatements.test(be.info);)be=be.prev;if(Fe.indent){var Mt=Fe.indent(me,be,De,j);if(typeof Mt=="number")return Mt}var Pt=be.prev&&be.prev.info=="switch";if(y.allmanIndentation&&/[{(]/.test(Be)){for(;be.type!="top"&&be.type!="}";)be=be.prev;return be.indented}return be.type=="statement"?be.indented+(Be=="{"?0:de):be.align&&(!v||be.type!=")")?be.column+(Ne?0:1):be.type==")"&&!Ne?be.indented+de:be.indented+(Ne?0:j)+(!Ne&&Pt&&!/^(?:case|default)\b/.test(De)?j:0)},electricInput:Oe?/^\s*(?:case .*?:|default:|\{\}?|\})$/:/^\s*[{}]$/,blockCommentStart:"/*",blockCommentEnd:"*/",blockCommentContinue:" * ",lineComment:"//",fold:"brace"}});function k(g){for(var y={},j=g.split(" "),de=0;de!?|\/#:@]/,hooks:{"@":function(g){return g.eatWhile(/[\w\$_]/),"meta"},'"':function(g,y){return g.match('""')?(y.tokenize=F,y.tokenize(g,y)):!1},"'":function(g){return g.match(/^(\\[^'\s]+|[^\\'])'/)?"string-2":(g.eatWhile(/[\w\$_\xa1-\uffff]/),"atom")},"=":function(g,y){var j=y.context;return j.type=="}"&&j.align&&g.eat(">")?(y.context=new pe(j.indented,j.column,j.type,j.info,null,j.prev),"operator"):!1},"/":function(g,y){return g.eat("*")?(y.tokenize=G(1),y.tokenize(g,y)):!1}},modeProps:{closeBrackets:{pairs:'()[]{}""',triples:'"'}}});function c(g){return function(y,j){for(var de=!1,v,d=!1;!y.eol();){if(!g&&!de&&y.match('"')){d=!0;break}if(g&&y.match('"""')){d=!0;break}v=y.next(),!de&&v=="$"&&y.match("{")&&y.skipTo("}"),de=!de&&v=="\\"&&!g}return(d||!g)&&(j.tokenize=null),"string"}}$("text/x-kotlin",{name:"clike",keywords:k("package as typealias class interface this super val operator var fun for is in This throw return annotation break continue object if else while do try when !in !is as? file import where by get set abstract enum open inner override private public internal protected catch finally out final vararg reified dynamic companion constructor init sealed field property receiver param sparam lateinit data inline noinline tailrec external annotation crossinline const operator infix suspend actual expect setparam value"),types:k("Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy LazyThreadSafetyMode LongArray Nothing ShortArray Unit"),intendSwitch:!1,indentStatements:!1,multiLineStrings:!0,number:/^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,blockKeywords:k("catch class do else finally for if where try while enum"),defKeywords:k("class val var object interface fun"),atoms:k("true false null this"),hooks:{"@":function(g){return g.eatWhile(/[\w\$_]/),"meta"},"*":function(g,y){return y.prevToken=="."?"variable":"operator"},'"':function(g,y){return y.tokenize=c(g.match('""')),y.tokenize(g,y)},"/":function(g,y){return g.eat("*")?(y.tokenize=G(1),y.tokenize(g,y)):!1},indent:function(g,y,j,de){var v=j&&j.charAt(0);if((g.prevToken=="}"||g.prevToken==")")&&j=="")return g.indented;if(g.prevToken=="operator"&&j!="}"&&g.context.type!="}"||g.prevToken=="variable"&&v=="."||(g.prevToken=="}"||g.prevToken==")")&&v==".")return de*2+y.indented;if(y.align&&y.type=="}")return y.indented+(g.context.type==(j||"").charAt(0)?0:de)}},modeProps:{closeBrackets:{triples:'"'}}}),$(["x-shader/x-vertex","x-shader/x-fragment"],{name:"clike",keywords:k("sampler1D sampler2D sampler3D samplerCube sampler1DShadow sampler2DShadow const attribute uniform varying break continue discard return for while do if else struct in out inout"),types:k("float int bool void vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 mat2 mat3 mat4"),blockKeywords:k("for while do if else struct"),builtin:k("radians degrees sin cos tan asin acos atan pow exp log exp2 sqrt inversesqrt abs sign floor ceil fract mod min max clamp mix step smoothstep length distance dot cross normalize ftransform faceforward reflect refract matrixCompMult lessThan lessThanEqual greaterThan greaterThanEqual equal notEqual any all not texture1D texture1DProj texture1DLod texture1DProjLod texture2D texture2DProj texture2DLod texture2DProjLod texture3D texture3DProj texture3DLod texture3DProjLod textureCube textureCubeLod shadow1D shadow2D shadow1DProj shadow2DProj shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod dFdx dFdy fwidth noise1 noise2 noise3 noise4"),atoms:k("true false gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_FogCoord gl_PointCoord gl_Position gl_PointSize gl_ClipVertex gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor gl_TexCoord gl_FogFragCoord gl_FragCoord gl_FrontFacing gl_FragData gl_FragDepth gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse gl_TextureMatrixTranspose gl_ModelViewMatrixInverseTranspose gl_ProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixInverseTranspose gl_TextureMatrixInverseTranspose gl_NormalScale gl_DepthRange gl_ClipPlane gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel gl_FrontLightModelProduct gl_BackLightModelProduct gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ gl_FogParameters gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits gl_MaxDrawBuffers"),indentSwitch:!1,hooks:{"#":N},modeProps:{fold:["brace","include"]}}),$("text/x-nesc",{name:"clike",keywords:k(Y+" as atomic async call command component components configuration event generic implementation includes interface module new norace nx_struct nx_union post provides signal task uses abstract extends"),types:ue,blockKeywords:k(w),atoms:k("null true false"),hooks:{"#":N},modeProps:{fold:["brace","include"]}}),$("text/x-objectivec",{name:"clike",keywords:k(Y+" "+S),types:O,builtin:k(R),blockKeywords:k(w+" @synthesize @try @catch @finally @autoreleasepool @synchronized"),defKeywords:k(M+" @interface @implementation @protocol @class"),dontIndentStatements:/^@.*$/,typeFirstDefinitions:!0,atoms:k("YES NO NULL Nil nil true false nullptr"),isReservedIdentifier:X,hooks:{"#":N,"*":z},modeProps:{fold:["brace","include"]}}),$("text/x-objectivec++",{name:"clike",keywords:k(Y+" "+S+" "+ne),types:O,builtin:k(R),blockKeywords:k(w+" @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"),defKeywords:k(M+" @interface @implementation @protocol @class class namespace"),dontIndentStatements:/^@.*$|^template$/,typeFirstDefinitions:!0,atoms:k("YES NO NULL Nil nil true false nullptr"),isReservedIdentifier:X,hooks:{"#":N,"*":z,u:p,U:p,L:p,R:p,0:q,1:q,2:q,3:q,4:q,5:q,6:q,7:q,8:q,9:q,token:function(g,y,j){if(j=="variable"&&g.peek()=="("&&(y.prevToken==";"||y.prevToken==null||y.prevToken=="}")&&W(g.current()))return"def"}},namespaceSeparator:"::",modeProps:{fold:["brace","include"]}}),$("text/x-squirrel",{name:"clike",keywords:k("base break clone continue const default delete enum extends function in class foreach local resume return this throw typeof yield constructor instanceof static"),types:ue,blockKeywords:k("case catch class else for foreach if switch try while"),defKeywords:k("function local class"),typeFirstDefinitions:!0,atoms:k("true false null"),hooks:{"#":N},modeProps:{fold:["brace","include"]}});var T=null;function C(g){return function(y,j){for(var de=!1,v,d=!1;!y.eol();){if(!de&&y.match('"')&&(g=="single"||y.match('""'))){d=!0;break}if(!de&&y.match("``")){T=C(g),d=!0;break}v=y.next(),de=g=="single"&&!de&&v=="\\"}return d&&(j.tokenize=null),"string"}}$("text/x-ceylon",{name:"clike",keywords:k("abstracts alias assembly assert assign break case catch class continue dynamic else exists extends finally for function given if import in interface is let module new nonempty object of out outer package return satisfies super switch then this throw try value void while"),types:function(g){var y=g.charAt(0);return y===y.toUpperCase()&&y!==y.toLowerCase()},blockKeywords:k("case catch class dynamic else finally for function if interface module new object switch try while"),defKeywords:k("class dynamic function interface module object package value"),builtin:k("abstract actual aliased annotation by default deprecated doc final formal late license native optional sealed see serializable shared suppressWarnings tagged throws variable"),isPunctuationChar:/[\[\]{}\(\),;\:\.`]/,isOperatorChar:/[+\-*&%=<>!?|^~:\/]/,numberStart:/[\d#$]/,number:/^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,multiLineStrings:!0,typeFirstDefinitions:!0,atoms:k("true false null larger smaller equal empty finished"),indentSwitch:!1,styleDefs:!1,hooks:{"@":function(g){return g.eatWhile(/[\w\$_]/),"meta"},'"':function(g,y){return y.tokenize=C(g.match('""')?"triple":"single"),y.tokenize(g,y)},"`":function(g,y){return!T||!g.match("`")?!1:(y.tokenize=T,T=null,y.tokenize(g,y))},"'":function(g){return g.eatWhile(/[\w\$_\xa1-\uffff]/),"atom"},token:function(g,y,j){if((j=="variable"||j=="type")&&y.prevToken==".")return"variable-2"}},modeProps:{fold:["brace","import"],closeBrackets:{triples:'"'}}})})})()),Ta.exports}tf();var Da={exports:{}},Ma={exports:{}},Fa;function rf(){return Fa||(Fa=1,(function(ct,xt){(function(b){b(mt())})(function(b){b.modeInfo=[{name:"APL",mime:"text/apl",mode:"apl",ext:["dyalog","apl"]},{name:"PGP",mimes:["application/pgp","application/pgp-encrypted","application/pgp-keys","application/pgp-signature"],mode:"asciiarmor",ext:["asc","pgp","sig"]},{name:"ASN.1",mime:"text/x-ttcn-asn",mode:"asn.1",ext:["asn","asn1"]},{name:"Asterisk",mime:"text/x-asterisk",mode:"asterisk",file:/^extensions\.conf$/i},{name:"Brainfuck",mime:"text/x-brainfuck",mode:"brainfuck",ext:["b","bf"]},{name:"C",mime:"text/x-csrc",mode:"clike",ext:["c","h","ino"]},{name:"C++",mime:"text/x-c++src",mode:"clike",ext:["cpp","c++","cc","cxx","hpp","h++","hh","hxx"],alias:["cpp"]},{name:"Cobol",mime:"text/x-cobol",mode:"cobol",ext:["cob","cpy","cbl"]},{name:"C#",mime:"text/x-csharp",mode:"clike",ext:["cs"],alias:["csharp","cs"]},{name:"Clojure",mime:"text/x-clojure",mode:"clojure",ext:["clj","cljc","cljx"]},{name:"ClojureScript",mime:"text/x-clojurescript",mode:"clojure",ext:["cljs"]},{name:"Closure Stylesheets (GSS)",mime:"text/x-gss",mode:"css",ext:["gss"]},{name:"CMake",mime:"text/x-cmake",mode:"cmake",ext:["cmake","cmake.in"],file:/^CMakeLists\.txt$/},{name:"CoffeeScript",mimes:["application/vnd.coffeescript","text/coffeescript","text/x-coffeescript"],mode:"coffeescript",ext:["coffee"],alias:["coffee","coffee-script"]},{name:"Common Lisp",mime:"text/x-common-lisp",mode:"commonlisp",ext:["cl","lisp","el"],alias:["lisp"]},{name:"Cypher",mime:"application/x-cypher-query",mode:"cypher",ext:["cyp","cypher"]},{name:"Cython",mime:"text/x-cython",mode:"python",ext:["pyx","pxd","pxi"]},{name:"Crystal",mime:"text/x-crystal",mode:"crystal",ext:["cr"]},{name:"CSS",mime:"text/css",mode:"css",ext:["css"]},{name:"CQL",mime:"text/x-cassandra",mode:"sql",ext:["cql"]},{name:"D",mime:"text/x-d",mode:"d",ext:["d"]},{name:"Dart",mimes:["application/dart","text/x-dart"],mode:"dart",ext:["dart"]},{name:"diff",mime:"text/x-diff",mode:"diff",ext:["diff","patch"]},{name:"Django",mime:"text/x-django",mode:"django"},{name:"Dockerfile",mime:"text/x-dockerfile",mode:"dockerfile",file:/^Dockerfile$/},{name:"DTD",mime:"application/xml-dtd",mode:"dtd",ext:["dtd"]},{name:"Dylan",mime:"text/x-dylan",mode:"dylan",ext:["dylan","dyl","intr"]},{name:"EBNF",mime:"text/x-ebnf",mode:"ebnf"},{name:"ECL",mime:"text/x-ecl",mode:"ecl",ext:["ecl"]},{name:"edn",mime:"application/edn",mode:"clojure",ext:["edn"]},{name:"Eiffel",mime:"text/x-eiffel",mode:"eiffel",ext:["e"]},{name:"Elm",mime:"text/x-elm",mode:"elm",ext:["elm"]},{name:"Embedded JavaScript",mime:"application/x-ejs",mode:"htmlembedded",ext:["ejs"]},{name:"Embedded Ruby",mime:"application/x-erb",mode:"htmlembedded",ext:["erb"]},{name:"Erlang",mime:"text/x-erlang",mode:"erlang",ext:["erl"]},{name:"Esper",mime:"text/x-esper",mode:"sql"},{name:"Factor",mime:"text/x-factor",mode:"factor",ext:["factor"]},{name:"FCL",mime:"text/x-fcl",mode:"fcl"},{name:"Forth",mime:"text/x-forth",mode:"forth",ext:["forth","fth","4th"]},{name:"Fortran",mime:"text/x-fortran",mode:"fortran",ext:["f","for","f77","f90","f95"]},{name:"F#",mime:"text/x-fsharp",mode:"mllike",ext:["fs"],alias:["fsharp"]},{name:"Gas",mime:"text/x-gas",mode:"gas",ext:["s"]},{name:"Gherkin",mime:"text/x-feature",mode:"gherkin",ext:["feature"]},{name:"GitHub Flavored Markdown",mime:"text/x-gfm",mode:"gfm",file:/^(readme|contributing|history)\.md$/i},{name:"Go",mime:"text/x-go",mode:"go",ext:["go"]},{name:"Groovy",mime:"text/x-groovy",mode:"groovy",ext:["groovy","gradle"],file:/^Jenkinsfile$/},{name:"HAML",mime:"text/x-haml",mode:"haml",ext:["haml"]},{name:"Haskell",mime:"text/x-haskell",mode:"haskell",ext:["hs"]},{name:"Haskell (Literate)",mime:"text/x-literate-haskell",mode:"haskell-literate",ext:["lhs"]},{name:"Haxe",mime:"text/x-haxe",mode:"haxe",ext:["hx"]},{name:"HXML",mime:"text/x-hxml",mode:"haxe",ext:["hxml"]},{name:"ASP.NET",mime:"application/x-aspx",mode:"htmlembedded",ext:["aspx"],alias:["asp","aspx"]},{name:"HTML",mime:"text/html",mode:"htmlmixed",ext:["html","htm","handlebars","hbs"],alias:["xhtml"]},{name:"HTTP",mime:"message/http",mode:"http"},{name:"IDL",mime:"text/x-idl",mode:"idl",ext:["pro"]},{name:"Pug",mime:"text/x-pug",mode:"pug",ext:["jade","pug"],alias:["jade"]},{name:"Java",mime:"text/x-java",mode:"clike",ext:["java"]},{name:"Java Server Pages",mime:"application/x-jsp",mode:"htmlembedded",ext:["jsp"],alias:["jsp"]},{name:"JavaScript",mimes:["text/javascript","text/ecmascript","application/javascript","application/x-javascript","application/ecmascript"],mode:"javascript",ext:["js"],alias:["ecmascript","js","node"]},{name:"JSON",mimes:["application/json","application/x-json"],mode:"javascript",ext:["json","map"],alias:["json5"]},{name:"JSON-LD",mime:"application/ld+json",mode:"javascript",ext:["jsonld"],alias:["jsonld"]},{name:"JSX",mime:"text/jsx",mode:"jsx",ext:["jsx"]},{name:"Jinja2",mime:"text/jinja2",mode:"jinja2",ext:["j2","jinja","jinja2"]},{name:"Julia",mime:"text/x-julia",mode:"julia",ext:["jl"],alias:["jl"]},{name:"Kotlin",mime:"text/x-kotlin",mode:"clike",ext:["kt"]},{name:"LESS",mime:"text/x-less",mode:"css",ext:["less"]},{name:"LiveScript",mime:"text/x-livescript",mode:"livescript",ext:["ls"],alias:["ls"]},{name:"Lua",mime:"text/x-lua",mode:"lua",ext:["lua"]},{name:"Markdown",mime:"text/x-markdown",mode:"markdown",ext:["markdown","md","mkd"]},{name:"mIRC",mime:"text/mirc",mode:"mirc"},{name:"MariaDB SQL",mime:"text/x-mariadb",mode:"sql"},{name:"Mathematica",mime:"text/x-mathematica",mode:"mathematica",ext:["m","nb","wl","wls"]},{name:"Modelica",mime:"text/x-modelica",mode:"modelica",ext:["mo"]},{name:"MUMPS",mime:"text/x-mumps",mode:"mumps",ext:["mps"]},{name:"MS SQL",mime:"text/x-mssql",mode:"sql"},{name:"mbox",mime:"application/mbox",mode:"mbox",ext:["mbox"]},{name:"MySQL",mime:"text/x-mysql",mode:"sql"},{name:"Nginx",mime:"text/x-nginx-conf",mode:"nginx",file:/nginx.*\.conf$/i},{name:"NSIS",mime:"text/x-nsis",mode:"nsis",ext:["nsh","nsi"]},{name:"NTriples",mimes:["application/n-triples","application/n-quads","text/n-triples"],mode:"ntriples",ext:["nt","nq"]},{name:"Objective-C",mime:"text/x-objectivec",mode:"clike",ext:["m"],alias:["objective-c","objc"]},{name:"Objective-C++",mime:"text/x-objectivec++",mode:"clike",ext:["mm"],alias:["objective-c++","objc++"]},{name:"OCaml",mime:"text/x-ocaml",mode:"mllike",ext:["ml","mli","mll","mly"]},{name:"Octave",mime:"text/x-octave",mode:"octave",ext:["m"]},{name:"Oz",mime:"text/x-oz",mode:"oz",ext:["oz"]},{name:"Pascal",mime:"text/x-pascal",mode:"pascal",ext:["p","pas"]},{name:"PEG.js",mime:"null",mode:"pegjs",ext:["jsonld"]},{name:"Perl",mime:"text/x-perl",mode:"perl",ext:["pl","pm"]},{name:"PHP",mimes:["text/x-php","application/x-httpd-php","application/x-httpd-php-open"],mode:"php",ext:["php","php3","php4","php5","php7","phtml"]},{name:"Pig",mime:"text/x-pig",mode:"pig",ext:["pig"]},{name:"Plain Text",mime:"text/plain",mode:"null",ext:["txt","text","conf","def","list","log"]},{name:"PLSQL",mime:"text/x-plsql",mode:"sql",ext:["pls"]},{name:"PostgreSQL",mime:"text/x-pgsql",mode:"sql"},{name:"PowerShell",mime:"application/x-powershell",mode:"powershell",ext:["ps1","psd1","psm1"]},{name:"Properties files",mime:"text/x-properties",mode:"properties",ext:["properties","ini","in"],alias:["ini","properties"]},{name:"ProtoBuf",mime:"text/x-protobuf",mode:"protobuf",ext:["proto"]},{name:"Python",mime:"text/x-python",mode:"python",ext:["BUILD","bzl","py","pyw"],file:/^(BUCK|BUILD)$/},{name:"Puppet",mime:"text/x-puppet",mode:"puppet",ext:["pp"]},{name:"Q",mime:"text/x-q",mode:"q",ext:["q"]},{name:"R",mime:"text/x-rsrc",mode:"r",ext:["r","R"],alias:["rscript"]},{name:"reStructuredText",mime:"text/x-rst",mode:"rst",ext:["rst"],alias:["rst"]},{name:"RPM Changes",mime:"text/x-rpm-changes",mode:"rpm"},{name:"RPM Spec",mime:"text/x-rpm-spec",mode:"rpm",ext:["spec"]},{name:"Ruby",mime:"text/x-ruby",mode:"ruby",ext:["rb"],alias:["jruby","macruby","rake","rb","rbx"]},{name:"Rust",mime:"text/x-rustsrc",mode:"rust",ext:["rs"]},{name:"SAS",mime:"text/x-sas",mode:"sas",ext:["sas"]},{name:"Sass",mime:"text/x-sass",mode:"sass",ext:["sass"]},{name:"Scala",mime:"text/x-scala",mode:"clike",ext:["scala"]},{name:"Scheme",mime:"text/x-scheme",mode:"scheme",ext:["scm","ss"]},{name:"SCSS",mime:"text/x-scss",mode:"css",ext:["scss"]},{name:"Shell",mimes:["text/x-sh","application/x-sh"],mode:"shell",ext:["sh","ksh","bash"],alias:["bash","sh","zsh"],file:/^PKGBUILD$/},{name:"Sieve",mime:"application/sieve",mode:"sieve",ext:["siv","sieve"]},{name:"Slim",mimes:["text/x-slim","application/x-slim"],mode:"slim",ext:["slim"]},{name:"Smalltalk",mime:"text/x-stsrc",mode:"smalltalk",ext:["st"]},{name:"Smarty",mime:"text/x-smarty",mode:"smarty",ext:["tpl"]},{name:"Solr",mime:"text/x-solr",mode:"solr"},{name:"SML",mime:"text/x-sml",mode:"mllike",ext:["sml","sig","fun","smackspec"]},{name:"Soy",mime:"text/x-soy",mode:"soy",ext:["soy"],alias:["closure template"]},{name:"SPARQL",mime:"application/sparql-query",mode:"sparql",ext:["rq","sparql"],alias:["sparul"]},{name:"Spreadsheet",mime:"text/x-spreadsheet",mode:"spreadsheet",alias:["excel","formula"]},{name:"SQL",mime:"text/x-sql",mode:"sql",ext:["sql"]},{name:"SQLite",mime:"text/x-sqlite",mode:"sql"},{name:"Squirrel",mime:"text/x-squirrel",mode:"clike",ext:["nut"]},{name:"Stylus",mime:"text/x-styl",mode:"stylus",ext:["styl"]},{name:"Swift",mime:"text/x-swift",mode:"swift",ext:["swift"]},{name:"sTeX",mime:"text/x-stex",mode:"stex"},{name:"LaTeX",mime:"text/x-latex",mode:"stex",ext:["text","ltx","tex"],alias:["tex"]},{name:"SystemVerilog",mime:"text/x-systemverilog",mode:"verilog",ext:["v","sv","svh"]},{name:"Tcl",mime:"text/x-tcl",mode:"tcl",ext:["tcl"]},{name:"Textile",mime:"text/x-textile",mode:"textile",ext:["textile"]},{name:"TiddlyWiki",mime:"text/x-tiddlywiki",mode:"tiddlywiki"},{name:"Tiki wiki",mime:"text/tiki",mode:"tiki"},{name:"TOML",mime:"text/x-toml",mode:"toml",ext:["toml"]},{name:"Tornado",mime:"text/x-tornado",mode:"tornado"},{name:"troff",mime:"text/troff",mode:"troff",ext:["1","2","3","4","5","6","7","8","9"]},{name:"TTCN",mime:"text/x-ttcn",mode:"ttcn",ext:["ttcn","ttcn3","ttcnpp"]},{name:"TTCN_CFG",mime:"text/x-ttcn-cfg",mode:"ttcn-cfg",ext:["cfg"]},{name:"Turtle",mime:"text/turtle",mode:"turtle",ext:["ttl"]},{name:"TypeScript",mime:"application/typescript",mode:"javascript",ext:["ts"],alias:["ts"]},{name:"TypeScript-JSX",mime:"text/typescript-jsx",mode:"jsx",ext:["tsx"],alias:["tsx"]},{name:"Twig",mime:"text/x-twig",mode:"twig"},{name:"Web IDL",mime:"text/x-webidl",mode:"webidl",ext:["webidl"]},{name:"VB.NET",mime:"text/x-vb",mode:"vb",ext:["vb"]},{name:"VBScript",mime:"text/vbscript",mode:"vbscript",ext:["vbs"]},{name:"Velocity",mime:"text/velocity",mode:"velocity",ext:["vtl"]},{name:"Verilog",mime:"text/x-verilog",mode:"verilog",ext:["v"]},{name:"VHDL",mime:"text/x-vhdl",mode:"vhdl",ext:["vhd","vhdl"]},{name:"Vue.js Component",mimes:["script/x-vue","text/x-vue"],mode:"vue",ext:["vue"]},{name:"XML",mimes:["application/xml","text/xml"],mode:"xml",ext:["xml","xsl","xsd","svg"],alias:["rss","wsdl","xsd"]},{name:"XQuery",mime:"application/xquery",mode:"xquery",ext:["xy","xquery"]},{name:"Yacas",mime:"text/x-yacas",mode:"yacas",ext:["ys"]},{name:"YAML",mimes:["text/x-yaml","text/yaml"],mode:"yaml",ext:["yaml","yml"],alias:["yml"]},{name:"Z80",mime:"text/x-z80",mode:"z80",ext:["z80"]},{name:"mscgen",mime:"text/x-mscgen",mode:"mscgen",ext:["mscgen","mscin","msc"]},{name:"xu",mime:"text/x-xu",mode:"mscgen",ext:["xu"]},{name:"msgenny",mime:"text/x-msgenny",mode:"mscgen",ext:["msgenny"]},{name:"WebAssembly",mime:"text/webassembly",mode:"wast",ext:["wat","wast"]}];for(var pe=0;pe-1&&te.substring(k+1,te.length);if(I)return b.findModeByExtension(I)},b.findModeByName=function(te){te=te.toLowerCase();for(var oe=0;oe` "'(~:]+/,ue=/^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/,O=/^\s*\[[^\]]+?\]:.*$/,w=/[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/,M=" ";function N(v,d,fe){return d.f=d.inline=fe,fe(v,d)}function z(v,d,fe){return d.f=d.block=fe,fe(v,d)}function X(v){return!v||!/\S/.test(v.string)}function q(v){if(v.linkTitle=!1,v.linkHref=!1,v.linkText=!1,v.em=!1,v.strong=!1,v.strikethrough=!1,v.quote=0,v.indentedCode=!1,v.f==W){var d=oe;if(!d){var fe=b.innerMode(te,v.htmlState);d=fe.mode.name=="xml"&&fe.state.tagStart===null&&!fe.state.context&&fe.state.tokenize.isInText}d&&(v.f=F,v.block=p,v.htmlState=null)}return v.trailingSpace=0,v.trailingSpaceNewLine=!1,v.prevLine=v.thisLine,v.thisLine={stream:null},null}function p(v,d){var fe=v.column()===d.indentation,Te=X(d.prevLine.stream),le=d.indentedCode,xe=d.prevLine.hr,Me=d.list!==!1,Fe=(d.listStack[d.listStack.length-1]||0)+3;d.indentedCode=!1;var Ce=d.indentation;if(d.indentationDiff===null&&(d.indentationDiff=d.indentation,Me)){for(d.list=null;Ce=4&&(le||d.prevLine.fencedCodeEnd||d.prevLine.header||Te))return v.skipToEnd(),d.indentedCode=!0,k.code;if(v.eatSpace())return null;if(fe&&d.indentation<=Fe&&(qe=v.match(R))&&qe[1].length<=6)return d.quote=0,d.header=qe[1].length,d.thisLine.header=!0,_.highlightFormatting&&(d.formatting="header"),d.f=d.inline,P(d);if(d.indentation<=Fe&&v.eat(">"))return d.quote=fe?1:d.quote+1,_.highlightFormatting&&(d.formatting="quote"),v.eatSpace(),P(d);if(!Oe&&!d.setext&&fe&&d.indentation<=Fe&&(qe=v.match(ne))){var Ve=qe[1]?"ol":"ul";return d.indentation=Ce+v.current().length,d.list=!0,d.quote=0,d.listStack.push(d.indentation),d.em=!1,d.strong=!1,d.code=!1,d.strikethrough=!1,_.taskLists&&v.match(S,!1)&&(d.taskList=!0),d.f=d.inline,_.highlightFormatting&&(d.formatting=["list","list-"+Ve]),P(d)}else{if(fe&&d.indentation<=Fe&&(qe=v.match(ue,!0)))return d.quote=0,d.fencedEndRE=new RegExp(qe[1]+"+ *$"),d.localMode=_.fencedCodeBlockHighlighting&&Q(qe[2]||_.fencedCodeBlockDefaultMode),d.localMode&&(d.localState=b.startState(d.localMode)),d.f=d.block=J,_.highlightFormatting&&(d.formatting="code-block"),d.code=-1,P(d);if(d.setext||(!ve||!Me)&&!d.quote&&d.list===!1&&!d.code&&!Oe&&!O.test(v.string)&&(qe=v.lookAhead(1))&&(qe=qe.match(A)))return d.setext?(d.header=d.setext,d.setext=0,v.skipToEnd(),_.highlightFormatting&&(d.formatting="header")):(d.header=qe[0].charAt(0)=="="?1:2,d.setext=d.header),d.thisLine.header=!0,d.f=d.inline,P(d);if(Oe)return v.skipToEnd(),d.hr=!0,d.thisLine.hr=!0,k.hr;if(v.peek()==="[")return N(v,d,g)}return N(v,d,d.inline)}function W(v,d){var fe=te.token(v,d.htmlState);if(!oe){var Te=b.innerMode(te,d.htmlState);(Te.mode.name=="xml"&&Te.state.tagStart===null&&!Te.state.context&&Te.state.tokenize.isInText||d.md_inside&&v.current().indexOf(">")>-1)&&(d.f=F,d.block=p,d.htmlState=null)}return fe}function J(v,d){var fe=d.listStack[d.listStack.length-1]||0,Te=d.indentation=v.quote?d.push(k.formatting+"-"+v.formatting[fe]+"-"+v.quote):d.push("error"))}if(v.taskOpen)return d.push("meta"),d.length?d.join(" "):null;if(v.taskClosed)return d.push("property"),d.length?d.join(" "):null;if(v.linkHref?d.push(k.linkHref,"url"):(v.strong&&d.push(k.strong),v.em&&d.push(k.em),v.strikethrough&&d.push(k.strikethrough),v.emoji&&d.push(k.emoji),v.linkText&&d.push(k.linkText),v.code&&d.push(k.code),v.image&&d.push(k.image),v.imageAltText&&d.push(k.imageAltText,"link"),v.imageMarker&&d.push(k.imageMarker)),v.header&&d.push(k.header,k.header+"-"+v.header),v.quote&&(d.push(k.quote),!_.maxBlockquoteDepth||_.maxBlockquoteDepth>=v.quote?d.push(k.quote+"-"+v.quote):d.push(k.quote+"-"+_.maxBlockquoteDepth)),v.list!==!1){var Te=(v.listStack.length-1)%3;Te?Te===1?d.push(k.list2):d.push(k.list3):d.push(k.list1)}return v.trailingSpaceNewLine?d.push("trailing-space-new-line"):v.trailingSpace&&d.push("trailing-space-"+(v.trailingSpace%2?"a":"b")),d.length?d.join(" "):null}function $(v,d){if(v.match(V,!0))return P(d)}function F(v,d){var fe=d.text(v,d);if(typeof fe<"u")return fe;if(d.list)return d.list=null,P(d);if(d.taskList){var Te=v.match(S,!0)[1]===" ";return Te?d.taskOpen=!0:d.taskClosed=!0,_.highlightFormatting&&(d.formatting="task"),d.taskList=!1,P(d)}if(d.taskOpen=!1,d.taskClosed=!1,d.header&&v.match(/^#+$/,!0))return _.highlightFormatting&&(d.formatting="header"),P(d);var le=v.next();if(d.linkTitle){d.linkTitle=!1;var xe=le;le==="("&&(xe=")"),xe=(xe+"").replace(/([.?*+^\[\]\\(){}|-])/g,"\\$1");var Me="^\\s*(?:[^"+xe+"\\\\]+|\\\\\\\\|\\\\.)"+xe;if(v.match(new RegExp(Me),!0))return k.linkHref}if(le==="`"){var Fe=d.formatting;_.highlightFormatting&&(d.formatting="code"),v.eatWhile("`");var Ce=v.current().length;if(d.code==0&&(!d.quote||Ce==1))return d.code=Ce,P(d);if(Ce==d.code){var ve=P(d);return d.code=0,ve}else return d.formatting=Fe,P(d)}else if(d.code)return P(d);if(le==="\\"&&(v.next(),_.highlightFormatting)){var Oe=P(d),qe=k.formatting+"-escape";return Oe?Oe+" "+qe:qe}if(le==="!"&&v.match(/\[[^\]]*\] ?(?:\(|\[)/,!1))return d.imageMarker=!0,d.image=!0,_.highlightFormatting&&(d.formatting="image"),P(d);if(le==="["&&d.imageMarker&&v.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/,!1))return d.imageMarker=!1,d.imageAltText=!0,_.highlightFormatting&&(d.formatting="image"),P(d);if(le==="]"&&d.imageAltText){_.highlightFormatting&&(d.formatting="image");var Oe=P(d);return d.imageAltText=!1,d.image=!1,d.inline=d.f=c,Oe}if(le==="["&&!d.image)return d.linkText&&v.match(/^.*?\]/)||(d.linkText=!0,_.highlightFormatting&&(d.formatting="link")),P(d);if(le==="]"&&d.linkText){_.highlightFormatting&&(d.formatting="link");var Oe=P(d);return d.linkText=!1,d.inline=d.f=v.match(/\(.*?\)| ?\[.*?\]/,!1)?c:F,Oe}if(le==="<"&&v.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/,!1)){d.f=d.inline=G,_.highlightFormatting&&(d.formatting="link");var Oe=P(d);return Oe?Oe+=" ":Oe="",Oe+k.linkInline}if(le==="<"&&v.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/,!1)){d.f=d.inline=G,_.highlightFormatting&&(d.formatting="link");var Oe=P(d);return Oe?Oe+=" ":Oe="",Oe+k.linkEmail}if(_.xml&&le==="<"&&v.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i,!1)){var Ve=v.string.indexOf(">",v.pos);if(Ve!=-1){var dt=v.string.substring(v.start,Ve);/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(dt)&&(d.md_inside=!0)}return v.backUp(1),d.htmlState=b.startState(te),z(v,d,W)}if(_.xml&&le==="<"&&v.match(/^\/\w*?>/))return d.md_inside=!1,"tag";if(le==="*"||le==="_"){for(var Pe=1,_e=v.pos==1?" ":v.string.charAt(v.pos-2);Pe<3&&v.eat(le);)Pe++;var Ue=v.peek()||" ",et=!/\s/.test(Ue)&&(!w.test(Ue)||/\s/.test(_e)||w.test(_e)),we=!/\s/.test(_e)&&(!w.test(_e)||/\s/.test(Ue)||w.test(Ue)),Ie=null,E=null;if(Pe%2&&(!d.em&&et&&(le==="*"||!we||w.test(_e))?Ie=!0:d.em==le&&we&&(le==="*"||!et||w.test(Ue))&&(Ie=!1)),Pe>1&&(!d.strong&&et&&(le==="*"||!we||w.test(_e))?E=!0:d.strong==le&&we&&(le==="*"||!et||w.test(Ue))&&(E=!1)),E!=null||Ie!=null){_.highlightFormatting&&(d.formatting=Ie==null?"strong":E==null?"em":"strong em"),Ie===!0&&(d.em=le),E===!0&&(d.strong=le);var ve=P(d);return Ie===!1&&(d.em=!1),E===!1&&(d.strong=!1),ve}}else if(le===" "&&(v.eat("*")||v.eat("_"))){if(v.peek()===" ")return P(d);v.backUp(1)}if(_.strikethrough){if(le==="~"&&v.eatWhile(le)){if(d.strikethrough){_.highlightFormatting&&(d.formatting="strikethrough");var ve=P(d);return d.strikethrough=!1,ve}else if(v.match(/^[^\s]/,!1))return d.strikethrough=!0,_.highlightFormatting&&(d.formatting="strikethrough"),P(d)}else if(le===" "&&v.match("~~",!0)){if(v.peek()===" ")return P(d);v.backUp(2)}}if(_.emoji&&le===":"&&v.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)){d.emoji=!0,_.highlightFormatting&&(d.formatting="emoji");var ee=P(d);return d.emoji=!1,ee}return le===" "&&(v.match(/^ +$/,!1)?d.trailingSpace++:d.trailingSpace&&(d.trailingSpaceNewLine=!0)),P(d)}function G(v,d){var fe=v.next();if(fe===">"){d.f=d.inline=F,_.highlightFormatting&&(d.formatting="link");var Te=P(d);return Te?Te+=" ":Te="",Te+k.linkInline}return v.match(/^[^>]+/,!0),k.linkInline}function c(v,d){if(v.eatSpace())return null;var fe=v.next();return fe==="("||fe==="["?(d.f=d.inline=C(fe==="("?")":"]"),_.highlightFormatting&&(d.formatting="link-string"),d.linkHref=!0,P(d)):"error"}var T={")":/^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,"]":/^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/};function C(v){return function(d,fe){var Te=d.next();if(Te===v){fe.f=fe.inline=F,_.highlightFormatting&&(fe.formatting="link-string");var le=P(fe);return fe.linkHref=!1,le}return d.match(T[v]),fe.linkHref=!0,P(fe)}}function g(v,d){return v.match(/^([^\]\\]|\\.)*\]:/,!1)?(d.f=y,v.next(),_.highlightFormatting&&(d.formatting="link"),d.linkText=!0,P(d)):N(v,d,F)}function y(v,d){if(v.match("]:",!0)){d.f=d.inline=j,_.highlightFormatting&&(d.formatting="link");var fe=P(d);return d.linkText=!1,fe}return v.match(/^([^\]\\]|\\.)+/,!0),k.linkText}function j(v,d){return v.eatSpace()?null:(v.match(/^[^\s]+/,!0),v.peek()===void 0?d.linkTitle=!0:v.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/,!0),d.f=d.inline=F,k.linkHref+" url")}var de={startState:function(){return{f:p,prevLine:{stream:null},thisLine:{stream:null},block:p,htmlState:null,indentation:0,inline:F,text:$,formatting:!1,linkText:!1,linkHref:!1,linkTitle:!1,code:0,em:!1,strong:!1,header:0,setext:0,hr:!1,taskList:!1,list:!1,listStack:[],quote:0,trailingSpace:0,trailingSpaceNewLine:!1,strikethrough:!1,emoji:!1,fencedEndRE:null}},copyState:function(v){return{f:v.f,prevLine:v.prevLine,thisLine:v.thisLine,block:v.block,htmlState:v.htmlState&&b.copyState(te,v.htmlState),indentation:v.indentation,localMode:v.localMode,localState:v.localMode?b.copyState(v.localMode,v.localState):null,inline:v.inline,text:v.text,formatting:!1,linkText:v.linkText,linkTitle:v.linkTitle,linkHref:v.linkHref,code:v.code,em:v.em,strong:v.strong,strikethrough:v.strikethrough,emoji:v.emoji,header:v.header,setext:v.setext,hr:v.hr,taskList:v.taskList,list:v.list,listStack:v.listStack.slice(0),quote:v.quote,indentedCode:v.indentedCode,trailingSpace:v.trailingSpace,trailingSpaceNewLine:v.trailingSpaceNewLine,md_inside:v.md_inside,fencedEndRE:v.fencedEndRE}},token:function(v,d){if(d.formatting=!1,v!=d.thisLine.stream){if(d.header=0,d.hr=!1,v.match(/^\s*$/,!0))return q(d),null;if(d.prevLine=d.thisLine,d.thisLine={stream:v},d.taskList=!1,d.trailingSpace=0,d.trailingSpaceNewLine=!1,!d.localState&&(d.f=d.block,d.f!=W)){var fe=v.match(/^\s*/,!0)[0].replace(/\t/g,M).length;if(d.indentation=fe,d.indentationDiff=null,fe>0)return null}}return d.f(v,d)},innerMode:function(v){return v.block==W?{state:v.htmlState,mode:te}:v.localState?{state:v.localState,mode:v.localMode}:{state:v,mode:de}},indent:function(v,d,fe){return v.block==W&&te.indent?te.indent(v.htmlState,d,fe):v.localState&&v.localMode.indent?v.localMode.indent(v.localState,d,fe):b.Pass},blankLine:q,getType:P,blockCommentStart:"",closeBrackets:"()[]{}''\"\"``",fold:"markdown"};return de},"xml"),b.defineMIME("text/markdown","markdown"),b.defineMIME("text/x-markdown","markdown")})})()),Da.exports}nf();var Na={exports:{}},Ea;function of(){return Ea||(Ea=1,(function(ct,xt){(function(b){b(mt())})(function(b){b.defineOption("placeholder","",function(I,Y,ne){var S=ne&&ne!=b.Init;if(Y&&!S)I.on("blur",oe),I.on("change",Q),I.on("swapDoc",Q),b.on(I.getInputField(),"compositionupdate",I.state.placeholderCompose=function(){te(I)}),Q(I);else if(!Y&&S){I.off("blur",oe),I.off("change",Q),I.off("swapDoc",Q),b.off(I.getInputField(),"compositionupdate",I.state.placeholderCompose),pe(I);var R=I.getWrapperElement();R.className=R.className.replace(" CodeMirror-empty","")}Y&&!I.hasFocus()&&oe(I)});function pe(I){I.state.placeholder&&(I.state.placeholder.parentNode.removeChild(I.state.placeholder),I.state.placeholder=null)}function _(I){pe(I);var Y=I.state.placeholder=document.createElement("pre");Y.style.cssText="height: 0; overflow: visible",Y.style.direction=I.getOption("direction"),Y.className="CodeMirror-placeholder CodeMirror-line-like";var ne=I.getOption("placeholder");typeof ne=="string"&&(ne=document.createTextNode(ne)),Y.appendChild(ne),I.display.lineSpace.insertBefore(Y,I.display.lineSpace.firstChild)}function te(I){setTimeout(function(){var Y=!1;if(I.lineCount()==1){var ne=I.getInputField();Y=ne.nodeName=="TEXTAREA"?!I.getLine(0).length:!/[^\u200b]/.test(ne.querySelector(".CodeMirror-line").textContent)}Y?_(I):pe(I)},20)}function oe(I){k(I)&&_(I)}function Q(I){var Y=I.getWrapperElement(),ne=k(I);Y.className=Y.className.replace(" CodeMirror-empty","")+(ne?" CodeMirror-empty":""),ne?_(I):pe(I)}function k(I){return I.lineCount()===1&&I.getLine(0)===""}})})()),Na.exports}of();var Oa={exports:{}},Pa;function lf(){return Pa||(Pa=1,(function(ct,xt){(function(b){b(mt())})(function(b){b.defineSimpleMode=function(S,R){b.defineMode(S,function(A){return b.simpleMode(A,R)})},b.simpleMode=function(S,R){pe(R,"start");var A={},V=R.meta||{},ue=!1;for(var O in R)if(O!=V&&R.hasOwnProperty(O))for(var w=A[O]=[],M=R[O],N=0;N2&&z.token&&typeof z.token!="string"){for(var p=2;p-1)return b.Pass;var O=A.indent.length-1,w=S[A.state];e:for(;;){for(var M=0;M",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<","<":">>",">":"<<"};function oe(S){return S&&S.bracketRegex||/[(){}[\]]/}function Q(S,R,A){var V=S.getLineHandle(R.line),ue=R.ch-1,O=A&&A.afterCursor;O==null&&(O=/(^| )cm-fat-cursor($| )/.test(S.getWrapperElement().className));var w=oe(A),M=!O&&ue>=0&&w.test(V.text.charAt(ue))&&te[V.text.charAt(ue)]||w.test(V.text.charAt(ue+1))&&te[V.text.charAt(++ue)];if(!M)return null;var N=M.charAt(1)==">"?1:-1;if(A&&A.strict&&N>0!=(ue==R.ch))return null;var z=S.getTokenTypeAt(_(R.line,ue+1)),X=k(S,_(R.line,ue+(N>0?1:0)),N,z,A);return X==null?null:{from:_(R.line,ue),to:X&&X.pos,match:X&&X.ch==M.charAt(0),forward:N>0}}function k(S,R,A,V,ue){for(var O=ue&&ue.maxScanLineLength||1e4,w=ue&&ue.maxScanLines||1e3,M=[],N=oe(ue),z=A>0?Math.min(R.line+w,S.lastLine()+1):Math.max(S.firstLine()-1,R.line-w),X=R.line;X!=z;X+=A){var q=S.getLine(X);if(q){var p=A>0?0:q.length-1,W=A>0?q.length:-1;if(!(q.length>O))for(X==R.line&&(p=R.ch-(A<0?1:0));p!=W;p+=A){var J=q.charAt(p);if(N.test(J)&&(V===void 0||(S.getTokenTypeAt(_(X,p+1))||"")==(V||""))){var P=te[J];if(P&&P.charAt(1)==">"==A>0)M.push(J);else if(M.length)M.pop();else return{pos:_(X,p),ch:J}}}}}return X-A==(A>0?S.lastLine():S.firstLine())?!1:null}function I(S,R,A){for(var V=S.state.matchBrackets.maxHighlightLineLength||1e3,ue=A&&A.highlightNonMatching,O=[],w=S.listSelections(),M=0;M`,triples:"",explode:"[]{}"},_=b.Pos;b.defineOption("autoCloseBrackets",!1,function(O,w,M){M&&M!=b.Init&&(O.removeKeyMap(oe),O.state.closeBrackets=null),w&&(Q(te(w,"pairs")),O.state.closeBrackets=w,O.addKeyMap(oe))});function te(O,w){return w=="pairs"&&typeof O=="string"?O:typeof O=="object"&&O[w]!=null?O[w]:pe[w]}var oe={Backspace:Y,Enter:ne};function Q(O){for(var w=0;w=0;z--){var q=N[z].head;O.replaceRange("",_(q.line,q.ch-1),_(q.line,q.ch+1),"+delete")}}function ne(O){var w=I(O),M=w&&te(w,"explode");if(!M||O.getOption("disableInput"))return b.Pass;for(var N=O.listSelections(),z=0;z0?{line:q.head.line,ch:q.head.ch+w}:{line:q.head.line-1};M.push({anchor:p,head:p})}O.setSelections(M,z)}function R(O){var w=b.cmpPos(O.anchor,O.head)>0;return{anchor:new _(O.anchor.line,O.anchor.ch+(w?-1:1)),head:new _(O.head.line,O.head.ch+(w?1:-1))}}function A(O,w){var M=I(O);if(!M||O.getOption("disableInput"))return b.Pass;var N=te(M,"pairs"),z=N.indexOf(w);if(z==-1)return b.Pass;for(var X=te(M,"closeBefore"),q=te(M,"triples"),p=N.charAt(z+1)==w,W=O.listSelections(),J=z%2==0,P,$=0;$=0&&O.getRange(G,_(G.line,G.ch+3))==w+w+w?c="skipThree":c="skip";else if(p&&G.ch>1&&q.indexOf(w)>=0&&O.getRange(_(G.line,G.ch-2),G)==w+w){if(G.ch>2&&/\bstring/.test(O.getTokenTypeAt(_(G.line,G.ch-2))))return b.Pass;c="addFour"}else if(p){var C=G.ch==0?" ":O.getRange(_(G.line,G.ch-1),G);if(!b.isWordChar(T)&&C!=w&&!b.isWordChar(C))c="both";else return b.Pass}else if(J&&(T.length===0||/\s/.test(T)||X.indexOf(T)>-1))c="both";else return b.Pass;if(!P)P=c;else if(P!=c)return b.Pass}var g=z%2?N.charAt(z-1):w,y=z%2?w:N.charAt(z+1);O.operation(function(){if(P=="skip")S(O,1);else if(P=="skipThree")S(O,3);else if(P=="surround"){for(var j=O.getSelections(),de=0;dep);W++){var J=w.getLine(q++);z=z==null?J:z+` -`+J}X=X*2,M.lastIndex=N.ch;var P=M.exec(z);if(P){var $=z.slice(0,P.index).split(` -`),F=P[0].split(` -`),G=N.line+$.length-1,c=$[$.length-1].length;return{from:pe(G,c),to:pe(G+F.length-1,F.length==1?c+F[0].length:F[F.length-1].length),match:P}}}}function I(w,M,N){for(var z,X=0;X<=w.length;){M.lastIndex=X;var q=M.exec(w);if(!q)break;var p=q.index+q[0].length;if(p>w.length-N)break;(!z||p>z.index+z[0].length)&&(z=q),X=q.index+1}return z}function Y(w,M,N){M=te(M,"g");for(var z=N.line,X=N.ch,q=w.firstLine();z>=q;z--,X=-1){var p=w.getLine(z),W=I(p,M,X<0?0:p.length-X);if(W)return{from:pe(z,W.index),to:pe(z,W.index+W[0].length),match:W}}}function ne(w,M,N){if(!oe(M))return Y(w,M,N);M=te(M,"gm");for(var z,X=1,q=w.getLine(N.line).length-N.ch,p=N.line,W=w.firstLine();p>=W;){for(var J=0;J=W;J++){var P=w.getLine(p--);z=z==null?P:P+` -`+z}X*=2;var $=I(z,M,q);if($){var F=z.slice(0,$.index).split(` -`),G=$[0].split(` -`),c=p+F.length,T=F[F.length-1].length;return{from:pe(c,T),to:pe(c+G.length-1,G.length==1?T+G[0].length:G[G.length-1].length),match:$}}}}var S,R;String.prototype.normalize?(S=function(w){return w.normalize("NFD").toLowerCase()},R=function(w){return w.normalize("NFD")}):(S=function(w){return w.toLowerCase()},R=function(w){return w});function A(w,M,N,z){if(w.length==M.length)return N;for(var X=0,q=N+Math.max(0,w.length-M.length);;){if(X==q)return X;var p=X+q>>1,W=z(w.slice(0,p)).length;if(W==N)return p;W>N?q=p:X=p+1}}function V(w,M,N,z){if(!M.length)return null;var X=z?S:R,q=X(M).split(/\r|\n\r?/);e:for(var p=N.line,W=N.ch,J=w.lastLine()+1-q.length;p<=J;p++,W=0){var P=w.getLine(p).slice(W),$=X(P);if(q.length==1){var F=$.indexOf(q[0]);if(F==-1)continue e;var N=A(P,$,F,X)+W;return{from:pe(p,A(P,$,F,X)+W),to:pe(p,A(P,$,F+q[0].length,X)+W)}}else{var G=$.length-q[0].length;if($.slice(G)!=q[0])continue e;for(var c=1;c=J;p--,W=-1){var P=w.getLine(p);W>-1&&(P=P.slice(0,W));var $=X(P);if(q.length==1){var F=$.lastIndexOf(q[0]);if(F==-1)continue e;return{from:pe(p,A(P,$,F,X)),to:pe(p,A(P,$,F+q[0].length,X))}}else{var G=q[q.length-1];if($.slice(0,G.length)!=G)continue e;for(var c=1,N=p-q.length+1;c(this.doc.getLine(M.line)||"").length&&(M.ch=0,M.line++)),b.cmpPos(M,this.doc.clipPos(M))!=0))return this.atOccurrence=!1;var N=this.matches(w,M);if(this.afterEmptyMatch=N&&b.cmpPos(N.from,N.to)==0,N)return this.pos=N,this.atOccurrence=!0,this.pos.match||!0;var z=pe(w?this.doc.firstLine():this.doc.lastLine()+1,0);return this.pos={from:z,to:z},this.atOccurrence=!1},from:function(){if(this.atOccurrence)return this.pos.from},to:function(){if(this.atOccurrence)return this.pos.to},replace:function(w,M){if(this.atOccurrence){var N=b.splitLines(w);this.doc.replaceRange(N,this.pos.from,this.pos.to,M),this.pos.to=pe(this.pos.from.line+N.length-1,N[N.length-1].length+(N.length==1?this.pos.from.ch:0))}}},b.defineExtension("getSearchCursor",function(w,M,N){return new O(this.doc,w,M,N)}),b.defineDocExtension("getSearchCursor",function(w,M,N){return new O(this,w,M,N)}),b.defineExtension("selectMatches",function(w,M){for(var N=[],z=this.getSearchCursor(w,this.getCursor("from"),M);z.findNext()&&!(b.cmpPos(z.to(),this.getCursor("to"))>0);)N.push({anchor:z.from(),head:z.to()});N.length&&this.setSelections(N,0)})})})()),Ha.exports}var qa={exports:{}},ja;function po(){return ja||(ja=1,(function(ct,xt){(function(b){b(mt())})(function(b){function pe(te,oe,Q){var k=te.getWrapperElement(),I;return I=k.appendChild(document.createElement("div")),Q?I.className="CodeMirror-dialog CodeMirror-dialog-bottom":I.className="CodeMirror-dialog CodeMirror-dialog-top",typeof oe=="string"?I.innerHTML=oe:I.appendChild(oe),b.addClass(k,"dialog-opened"),I}function _(te,oe){te.state.currentNotificationClose&&te.state.currentNotificationClose(),te.state.currentNotificationClose=oe}b.defineExtension("openDialog",function(te,oe,Q){Q||(Q={}),_(this,null);var k=pe(this,te,Q.bottom),I=!1,Y=this;function ne(A){if(typeof A=="string")S.value=A;else{if(I)return;I=!0,b.rmClass(k.parentNode,"dialog-opened"),k.parentNode.removeChild(k),Y.focus(),Q.onClose&&Q.onClose(k)}}var S=k.getElementsByTagName("input")[0],R;return S?(S.focus(),Q.value&&(S.value=Q.value,Q.selectValueOnOpen!==!1&&S.select()),Q.onInput&&b.on(S,"input",function(A){Q.onInput(A,S.value,ne)}),Q.onKeyUp&&b.on(S,"keyup",function(A){Q.onKeyUp(A,S.value,ne)}),b.on(S,"keydown",function(A){Q&&Q.onKeyDown&&Q.onKeyDown(A,S.value,ne)||((A.keyCode==27||Q.closeOnEnter!==!1&&A.keyCode==13)&&(S.blur(),b.e_stop(A),ne()),A.keyCode==13&&oe(S.value,A))}),Q.closeOnBlur!==!1&&b.on(k,"focusout",function(A){A.relatedTarget!==null&&ne()})):(R=k.getElementsByTagName("button")[0])&&(b.on(R,"click",function(){ne(),Y.focus()}),Q.closeOnBlur!==!1&&b.on(R,"blur",ne),R.focus()),ne}),b.defineExtension("openConfirm",function(te,oe,Q){_(this,null);var k=pe(this,te,Q&&Q.bottom),I=k.getElementsByTagName("button"),Y=!1,ne=this,S=1;function R(){Y||(Y=!0,b.rmClass(k.parentNode,"dialog-opened"),k.parentNode.removeChild(k),ne.focus())}I[0].focus();for(var A=0;Ap.cursorCoords(y,"window").top&&((G=j).style.opacity=.4)}))};k(p,w(p),F,c,function(T,C){var g=b.keyName(T),y=p.getOption("extraKeys"),j=y&&y[g]||b.keyMap[p.getOption("keyMap")][g];j=="findNext"||j=="findPrev"||j=="findPersistentNext"||j=="findPersistentPrev"?(b.e_stop(T),R(p,te(p),C),p.execCommand(j)):(j=="find"||j=="findPersistent")&&(b.e_stop(T),c(C,T))}),P&&F&&(R(p,$,F),V(p,W))}else I(p,w(p),"Search for:",F,function(T){T&&!$.query&&p.operation(function(){R(p,$,T),$.posFrom=$.posTo=p.getCursor(),V(p,W)})})}function V(p,W,J){p.operation(function(){var P=te(p),$=Q(p,P.query,W?P.posFrom:P.posTo);!$.find(W)&&($=Q(p,P.query,W?b.Pos(p.lastLine()):b.Pos(p.firstLine(),0)),!$.find(W))||(p.setSelection($.from(),$.to()),p.scrollIntoView({from:$.from(),to:$.to()},20),P.posFrom=$.from(),P.posTo=$.to(),J&&J($.from(),$.to()))})}function ue(p){p.operation(function(){var W=te(p);W.lastQuery=W.query,W.query&&(W.query=W.queryText=null,p.removeOverlay(W.overlay),W.annotate&&(W.annotate.clear(),W.annotate=null))})}function O(p,W){var J=p?document.createElement(p):document.createDocumentFragment();for(var P in W)J[P]=W[P];for(var $=2;$ '+oe.phrase("(Use line:column or scroll% syntax)")+""}function te(oe,Q){var k=Number(Q);return/^[-+]/.test(Q)?oe.getCursor().line+k:k-1}b.commands.jumpToLine=function(oe){var Q=oe.getCursor();pe(oe,_(oe),oe.phrase("Jump to line:"),Q.line+1+":"+Q.ch,function(k){if(k){var I;if(I=/^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(k))oe.setCursor(te(oe,I[1]),Number(I[2]));else if(I=/^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(k)){var Y=Math.round(oe.lineCount()*Number(I[1])/100);/^[-+]/.test(I[1])&&(Y=Q.line+Y+1),oe.setCursor(Y-1,Q.ch)}else(I=/^\s*\:?\s*([\+\-]?\d+)\s*/.exec(k))&&oe.setCursor(te(oe,I[1]),Q.ch)}})},b.keyMap.default["Alt-G"]="jumpToLine"})})()),Ua.exports}ff();po();export{df as default}; diff --git a/apps/web/playwright-report/trace/assets/defaultSettingsView-CJSZINFr.js b/apps/web/playwright-report/trace/assets/defaultSettingsView-CJSZINFr.js deleted file mode 100644 index ae17ee850..000000000 --- a/apps/web/playwright-report/trace/assets/defaultSettingsView-CJSZINFr.js +++ /dev/null @@ -1,266 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./codeMirrorModule-a5XoALAZ.js","../codeMirrorModule.DYBRYzYX.css"])))=>i.map(i=>d[i]); -var rx=Object.defineProperty;var ax=(n,e,i)=>e in n?rx(n,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):n[e]=i;var Ma=(n,e,i)=>ax(n,typeof e!="symbol"?e+"":e,i);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const o of l)if(o.type==="childList")for(const u of o.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&r(u)}).observe(document,{childList:!0,subtree:!0});function i(l){const o={};return l.integrity&&(o.integrity=l.integrity),l.referrerPolicy&&(o.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?o.credentials="include":l.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function r(l){if(l.ep)return;l.ep=!0;const o=i(l);fetch(l.href,o)}})();function lx(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var th={exports:{}},Oa={};/** - * @license React - * react-jsx-runtime.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Hy;function ox(){if(Hy)return Oa;Hy=1;var n=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function i(r,l,o){var u=null;if(o!==void 0&&(u=""+o),l.key!==void 0&&(u=""+l.key),"key"in l){o={};for(var f in l)f!=="key"&&(o[f]=l[f])}else o=l;return l=o.ref,{$$typeof:n,type:r,key:u,ref:l!==void 0?l:null,props:o}}return Oa.Fragment=e,Oa.jsx=i,Oa.jsxs=i,Oa}var qy;function cx(){return qy||(qy=1,th.exports=ox()),th.exports}var S=cx(),nh={exports:{}},fe={};/** - * @license React - * react.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var $y;function ux(){if($y)return fe;$y=1;var n=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),i=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),l=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),u=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),m=Symbol.for("react.activity"),w=Symbol.iterator;function v(k){return k===null||typeof k!="object"?null:(k=w&&k[w]||k["@@iterator"],typeof k=="function"?k:null)}var E={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},x=Object.assign,_={};function N(k,Y,Z){this.props=k,this.context=Y,this.refs=_,this.updater=Z||E}N.prototype.isReactComponent={},N.prototype.setState=function(k,Y){if(typeof k!="object"&&typeof k!="function"&&k!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,k,Y,"setState")},N.prototype.forceUpdate=function(k){this.updater.enqueueForceUpdate(this,k,"forceUpdate")};function C(){}C.prototype=N.prototype;function $(k,Y,Z){this.props=k,this.context=Y,this.refs=_,this.updater=Z||E}var I=$.prototype=new C;I.constructor=$,x(I,N.prototype),I.isPureReactComponent=!0;var D=Array.isArray;function K(){}var Q={H:null,A:null,T:null,S:null},q=Object.prototype.hasOwnProperty;function j(k,Y,Z){var ee=Z.ref;return{$$typeof:n,type:k,key:Y,ref:ee!==void 0?ee:null,props:Z}}function ne(k,Y){return j(k.type,Y,k.props)}function le(k){return typeof k=="object"&&k!==null&&k.$$typeof===n}function V(k){var Y={"=":"=0",":":"=2"};return"$"+k.replace(/[=:]/g,function(Z){return Y[Z]})}var J=/\/+/g;function W(k,Y){return typeof k=="object"&&k!==null&&k.key!=null?V(""+k.key):Y.toString(36)}function Ae(k){switch(k.status){case"fulfilled":return k.value;case"rejected":throw k.reason;default:switch(typeof k.status=="string"?k.then(K,K):(k.status="pending",k.then(function(Y){k.status==="pending"&&(k.status="fulfilled",k.value=Y)},function(Y){k.status==="pending"&&(k.status="rejected",k.reason=Y)})),k.status){case"fulfilled":return k.value;case"rejected":throw k.reason}}throw k}function B(k,Y,Z,ee,ue){var re=typeof k;(re==="undefined"||re==="boolean")&&(k=null);var xe=!1;if(k===null)xe=!0;else switch(re){case"bigint":case"string":case"number":xe=!0;break;case"object":switch(k.$$typeof){case n:case e:xe=!0;break;case y:return xe=k._init,B(xe(k._payload),Y,Z,ee,ue)}}if(xe)return ue=ue(k),xe=ee===""?"."+W(k,0):ee,D(ue)?(Z="",xe!=null&&(Z=xe.replace(J,"$&/")+"/"),B(ue,Y,Z,"",function(Bi){return Bi})):ue!=null&&(le(ue)&&(ue=ne(ue,Z+(ue.key==null||k&&k.key===ue.key?"":(""+ue.key).replace(J,"$&/")+"/")+xe)),Y.push(ue)),1;xe=0;var tt=ee===""?".":ee+":";if(D(k))for(var Re=0;Re{let u=!1;return n().then(f=>{u||o(f)}),()=>{u=!0}},e),l}function gs(){const n=gt.useRef(null),[e]=Eh(n);return[e,n]}function Eh(n){const[e,i]=gt.useState(new DOMRect(0,0,10,10)),r=gt.useCallback(()=>{const l=n==null?void 0:n.current;l&&i(l.getBoundingClientRect())},[n]);return gt.useLayoutEffect(()=>{const l=n==null?void 0:n.current;if(!l)return;r();const o=new ResizeObserver(r);return o.observe(l),window.addEventListener("resize",r),()=>{o.disconnect(),window.removeEventListener("resize",r)}},[r,n]),[e,r]}function Et(n){if(n<0||!isFinite(n))return"-";if(n===0)return"0";if(n<1e3)return n.toFixed(0)+"ms";const e=n/1e3;if(e<60)return e.toFixed(1)+"s";const i=e/60;if(i<60)return i.toFixed(1)+"m";const r=i/60;return r<24?r.toFixed(1)+"h":(r/24).toFixed(1)+"d"}function fx(n){if(n<0||!isFinite(n))return"-";if(n===0)return"0";if(n<1e3)return n.toFixed(0);const e=n/1024;if(e<1e3)return e.toFixed(1)+"K";const i=e/1024;return i<1e3?i.toFixed(1)+"M":(i/1024).toFixed(1)+"G"}function F0(n,e,i,r,l){let o=0,u=n.length;for(;o>1;i(e,n[f])>=0?o=f+1:u=f}return u}function Vy(n){const e=document.createElement("textarea");e.style.position="absolute",e.style.zIndex="-1000",e.value=n,document.body.appendChild(e),e.select(),document.execCommand("copy"),e.remove()}function on(n,e){n&&(e=ls.getObject(n,e));const[i,r]=gt.useState(e),l=gt.useCallback(o=>{n?ls.setObject(n,o):r(o)},[n,r]);return gt.useEffect(()=>{if(n){const o=()=>r(ls.getObject(n,e));return ls.onChangeEmitter.addEventListener(n,o),()=>ls.onChangeEmitter.removeEventListener(n,o)}},[e,n]),[i,l]}const Ah=new Map,Q0=new Map;let tc;function ki(n,e){const[i,r]=gt.useState();Q0.set(n,{setter:r,defaultValue:e});const l=gt.useCallback(o=>{const u=Ah.get(tc||"default")||{};u[n]=o,Ah.set(tc||"default",u),r(o)},[n]);return[i,l]}function hx(n){if(tc===n)return;tc=n;const e=Ah.get(n)||{};for(const[i,r]of Q0.entries())r.setter(e[i]||r.defaultValue)}class dx{constructor(){this.onChangeEmitter=new EventTarget}getString(e,i){return localStorage[e]||i}setString(e,i){var r;localStorage[e]=i,this.onChangeEmitter.dispatchEvent(new Event(e)),(r=window.saveSettings)==null||r.call(window)}getObject(e,i){if(!localStorage[e])return i;try{return JSON.parse(localStorage[e])}catch{return i}}setObject(e,i){var r;localStorage[e]=JSON.stringify(i),this.onChangeEmitter.dispatchEvent(new Event(e)),(r=window.saveSettings)==null||r.call(window)}}const ls=new dx;function Fe(...n){return n.filter(Boolean).join(" ")}function J0(n){n&&(n!=null&&n.scrollIntoViewIfNeeded?n.scrollIntoViewIfNeeded(!1):n==null||n.scrollIntoView())}const Gy="\\u0000-\\u0020\\u007f-\\u009f",P0=new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|www\\.)[^\\s"+Gy+'"]{2,}[^\\s'+Gy+`"')}\\],:;.!?]`,"ug");function px(){const[n,e]=gt.useState(!1),i=gt.useCallback(()=>{const r=[];return e(l=>(r.push(setTimeout(()=>e(!1),1e3)),l?(r.push(setTimeout(()=>e(!0),50)),!1):!0)),()=>r.forEach(clearTimeout)},[e]);return[n,i]}const gx="system",Z0="theme",mx=[{label:"Dark mode",value:"dark-mode"},{label:"Light mode",value:"light-mode"},{label:"System",value:"system"}],W0=window.matchMedia("(prefers-color-scheme: dark)");function LC(){document.playwrightThemeInitialized||(document.playwrightThemeInitialized=!0,document.defaultView.addEventListener("focus",n=>{n.target.document.nodeType===Node.DOCUMENT_NODE&&document.body.classList.remove("inactive")},!1),document.defaultView.addEventListener("blur",n=>{document.body.classList.add("inactive")},!1),Nh(Ch()),W0.addEventListener("change",()=>{Nh(Ch())}))}const Fh=new Set;function Nh(n){const e=yx(),i=n==="system"?W0.matches?"dark-mode":"light-mode":n;if(e!==i){e&&document.documentElement.classList.remove(e),document.documentElement.classList.add(i);for(const r of Fh)r(i)}}function RC(n){Fh.add(n)}function DC(n){Fh.delete(n)}function Ch(){return ls.getString(Z0,gx)}function yx(){return document.documentElement.classList.contains("dark-mode")?"dark-mode":document.documentElement.classList.contains("light-mode")?"light-mode":null}function bx(){const[n,e]=gt.useState(Ch());return gt.useEffect(()=>{ls.setString(Z0,n),Nh(n)},[n]),[n,e]}var ih={exports:{}},ja={},sh={exports:{}},rh={};/** - * @license React - * scheduler.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Ky;function vx(){return Ky||(Ky=1,(function(n){function e(B,P){var se=B.length;B.push(P);e:for(;0>>1,we=B[Se];if(0>>1;Sel(Z,se))eel(ue,Z)?(B[Se]=ue,B[ee]=se,Se=ee):(B[Se]=Z,B[Y]=se,Se=Y);else if(eel(ue,se))B[Se]=ue,B[ee]=se,Se=ee;else break e}}return P}function l(B,P){var se=B.sortIndex-P.sortIndex;return se!==0?se:B.id-P.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;n.unstable_now=function(){return o.now()}}else{var u=Date,f=u.now();n.unstable_now=function(){return u.now()-f}}var h=[],g=[],y=1,m=null,w=3,v=!1,E=!1,x=!1,_=!1,N=typeof setTimeout=="function"?setTimeout:null,C=typeof clearTimeout=="function"?clearTimeout:null,$=typeof setImmediate<"u"?setImmediate:null;function I(B){for(var P=i(g);P!==null;){if(P.callback===null)r(g);else if(P.startTime<=B)r(g),P.sortIndex=P.expirationTime,e(h,P);else break;P=i(g)}}function D(B){if(x=!1,I(B),!E)if(i(h)!==null)E=!0,K||(K=!0,V());else{var P=i(g);P!==null&&Ae(D,P.startTime-B)}}var K=!1,Q=-1,q=5,j=-1;function ne(){return _?!0:!(n.unstable_now()-jB&&ne());){var Se=m.callback;if(typeof Se=="function"){m.callback=null,w=m.priorityLevel;var we=Se(m.expirationTime<=B);if(B=n.unstable_now(),typeof we=="function"){m.callback=we,I(B),P=!0;break t}m===i(h)&&r(h),I(B)}else r(h);m=i(h)}if(m!==null)P=!0;else{var k=i(g);k!==null&&Ae(D,k.startTime-B),P=!1}}break e}finally{m=null,w=se,v=!1}P=void 0}}finally{P?V():K=!1}}}var V;if(typeof $=="function")V=function(){$(le)};else if(typeof MessageChannel<"u"){var J=new MessageChannel,W=J.port2;J.port1.onmessage=le,V=function(){W.postMessage(null)}}else V=function(){N(le,0)};function Ae(B,P){Q=N(function(){B(n.unstable_now())},P)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(B){B.callback=null},n.unstable_forceFrameRate=function(B){0>B||125Se?(B.sortIndex=se,e(g,B),i(h)===null&&B===i(g)&&(x?(C(Q),Q=-1):x=!0,Ae(D,se-Se))):(B.sortIndex=we,e(h,B),E||v||(E=!0,K||(K=!0,V()))),B},n.unstable_shouldYield=ne,n.unstable_wrapCallback=function(B){var P=w;return function(){var se=w;w=P;try{return B.apply(this,arguments)}finally{w=se}}}})(rh)),rh}var Yy;function Sx(){return Yy||(Yy=1,sh.exports=vx()),sh.exports}var ah={exports:{}},yt={};/** - * @license React - * react-dom.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Xy;function wx(){if(Xy)return yt;Xy=1;var n=Xh();function e(h){var g="https://react.dev/errors/"+h;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}return n(),ah.exports=wx(),ah.exports}/** - * @license React - * react-dom-client.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Qy;function _x(){if(Qy)return ja;Qy=1;var n=Sx(),e=Xh(),i=xx();function r(t){var s="https://react.dev/errors/"+t;if(1we||(t.current=Se[we],Se[we]=null,we--)}function Z(t,s){we++,Se[we]=t.current,t.current=s}var ee=k(null),ue=k(null),re=k(null),xe=k(null);function tt(t,s){switch(Z(re,s),Z(ue,t),Z(ee,null),s.nodeType){case 9:case 11:t=(t=s.documentElement)&&(t=t.namespaceURI)?oy(t):0;break;default:if(t=s.tagName,s=s.namespaceURI)s=oy(s),t=cy(s,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}Y(ee),Z(ee,t)}function Re(){Y(ee),Y(ue),Y(re)}function Bi(t){t.memoizedState!==null&&Z(xe,t);var s=ee.current,a=cy(s,t.type);s!==a&&(Z(ue,t),Z(ee,a))}function kn(t){ue.current===t&&(Y(ee),Y(ue)),xe.current===t&&(Y(xe),Aa._currentValue=se)}var hn,Dr;function nt(t){if(hn===void 0)try{throw Error()}catch(a){var s=a.stack.trim().match(/\n( *(at )?)/);hn=s&&s[1]||"",Dr=-1)":-1d||A[c]!==R[d]){var G=` -`+A[c].replace(" at new "," at ");return t.displayName&&G.includes("")&&(G=G.replace("",t.displayName)),G}while(1<=c&&0<=d);break}}}finally{vs=!1,Error.prepareStackTrace=a}return(a=t?t.displayName||t.name:"")?nt(a):""}function zc(t,s){switch(t.tag){case 26:case 27:case 5:return nt(t.type);case 16:return nt("Lazy");case 13:return t.child!==s&&s!==null?nt("Suspense Fallback"):nt("Suspense");case 19:return nt("SuspenseList");case 0:case 15:return zr(t.type,!1);case 11:return zr(t.type.render,!1);case 1:return zr(t.type,!0);case 31:return nt("Activity");default:return""}}function Ss(t){try{var s="",a=null;do s+=zc(t,a),a=t,t=t.return;while(t);return s}catch(c){return` -Error generating stack: `+c.message+` -`+c.stack}}var Ui=Object.prototype.hasOwnProperty,ni=n.unstable_scheduleCallback,Br=n.unstable_cancelCallback,ii=n.unstable_shouldYield,Bc=n.unstable_requestPaint,St=n.unstable_now,Uc=n.unstable_getCurrentPriorityLevel,dl=n.unstable_ImmediatePriority,Ur=n.unstable_UserBlockingPriority,si=n.unstable_NormalPriority,Hc=n.unstable_LowPriority,pl=n.unstable_IdlePriority,qc=n.log,Hi=n.unstable_setDisableYieldValue,Mn=null,rt=null;function vn(t){if(typeof qc=="function"&&Hi(t),rt&&typeof rt.setStrictMode=="function")try{rt.setStrictMode(Mn,t)}catch{}}var wt=Math.clz32?Math.clz32:ce,$c=Math.log,gl=Math.LN2;function ce(t){return t>>>=0,t===0?32:31-($c(t)/gl|0)|0}var Sn=256,Ft=262144,ml=4194304;function qi(t){var s=t&42;if(s!==0)return s;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function yl(t,s,a){var c=t.pendingLanes;if(c===0)return 0;var d=0,p=t.suspendedLanes,b=t.pingedLanes;t=t.warmLanes;var T=c&134217727;return T!==0?(c=T&~p,c!==0?d=qi(c):(b&=T,b!==0?d=qi(b):a||(a=T&~t,a!==0&&(d=qi(a))))):(T=c&~p,T!==0?d=qi(T):b!==0?d=qi(b):a||(a=c&~t,a!==0&&(d=qi(a)))),d===0?0:s!==0&&s!==d&&(s&p)===0&&(p=d&-d,a=s&-s,p>=a||p===32&&(a&4194048)!==0)?s:d}function Hr(t,s){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&s)===0}function YS(t,s){switch(t){case 1:case 2:case 4:case 8:case 64:return s+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return s+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Vd(){var t=ml;return ml<<=1,(ml&62914560)===0&&(ml=4194304),t}function Ic(t){for(var s=[],a=0;31>a;a++)s.push(t);return s}function qr(t,s){t.pendingLanes|=s,s!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function XS(t,s,a,c,d,p){var b=t.pendingLanes;t.pendingLanes=a,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=a,t.entangledLanes&=a,t.errorRecoveryDisabledLanes&=a,t.shellSuspendCounter=0;var T=t.entanglements,A=t.expirationTimes,R=t.hiddenUpdates;for(a=b&~a;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var WS=/[\n"\\]/g;function Jt(t){return t.replace(WS,function(s){return"\\"+s.charCodeAt(0).toString(16)+" "})}function Fc(t,s,a,c,d,p,b,T){t.name="",b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"?t.type=b:t.removeAttribute("type"),s!=null?b==="number"?(s===0&&t.value===""||t.value!=s)&&(t.value=""+Qt(s)):t.value!==""+Qt(s)&&(t.value=""+Qt(s)):b!=="submit"&&b!=="reset"||t.removeAttribute("value"),s!=null?Qc(t,b,Qt(s)):a!=null?Qc(t,b,Qt(a)):c!=null&&t.removeAttribute("value"),d==null&&p!=null&&(t.defaultChecked=!!p),d!=null&&(t.checked=d&&typeof d!="function"&&typeof d!="symbol"),T!=null&&typeof T!="function"&&typeof T!="symbol"&&typeof T!="boolean"?t.name=""+Qt(T):t.removeAttribute("name")}function np(t,s,a,c,d,p,b,T){if(p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"&&(t.type=p),s!=null||a!=null){if(!(p!=="submit"&&p!=="reset"||s!=null)){Xc(t);return}a=a!=null?""+Qt(a):"",s=s!=null?""+Qt(s):a,T||s===t.value||(t.value=s),t.defaultValue=s}c=c??d,c=typeof c!="function"&&typeof c!="symbol"&&!!c,t.checked=T?t.checked:!!c,t.defaultChecked=!!c,b!=null&&typeof b!="function"&&typeof b!="symbol"&&typeof b!="boolean"&&(t.name=b),Xc(t)}function Qc(t,s,a){s==="number"&&Sl(t.ownerDocument)===t||t.defaultValue===""+a||(t.defaultValue=""+a)}function As(t,s,a,c){if(t=t.options,s){s={};for(var d=0;d"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),eu=!1;if(Ln)try{var Gr={};Object.defineProperty(Gr,"passive",{get:function(){eu=!0}}),window.addEventListener("test",Gr,Gr),window.removeEventListener("test",Gr,Gr)}catch{eu=!1}var ai=null,tu=null,xl=null;function cp(){if(xl)return xl;var t,s=tu,a=s.length,c,d="value"in ai?ai.value:ai.textContent,p=d.length;for(t=0;t=Xr),gp=" ",mp=!1;function yp(t,s){switch(t){case"keyup":return N1.indexOf(s.keyCode)!==-1;case"keydown":return s.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function bp(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ms=!1;function k1(t,s){switch(t){case"compositionend":return bp(s);case"keypress":return s.which!==32?null:(mp=!0,gp);case"textInput":return t=s.data,t===gp&&mp?null:t;default:return null}}function M1(t,s){if(Ms)return t==="compositionend"||!au&&yp(t,s)?(t=cp(),xl=tu=ai=null,Ms=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(s.ctrlKey||s.altKey||s.metaKey)||s.ctrlKey&&s.altKey){if(s.char&&1=s)return{node:a,offset:s-t};t=c}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Ap(a)}}function Cp(t,s){return t&&s?t===s?!0:t&&t.nodeType===3?!1:s&&s.nodeType===3?Cp(t,s.parentNode):"contains"in t?t.contains(s):t.compareDocumentPosition?!!(t.compareDocumentPosition(s)&16):!1:!1}function kp(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var s=Sl(t.document);s instanceof t.HTMLIFrameElement;){try{var a=typeof s.contentWindow.location.href=="string"}catch{a=!1}if(a)t=s.contentWindow;else break;s=Sl(t.document)}return s}function cu(t){var s=t&&t.nodeName&&t.nodeName.toLowerCase();return s&&(s==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||s==="textarea"||t.contentEditable==="true")}var U1=Ln&&"documentMode"in document&&11>=document.documentMode,Os=null,uu=null,Pr=null,fu=!1;function Mp(t,s,a){var c=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;fu||Os==null||Os!==Sl(c)||(c=Os,"selectionStart"in c&&cu(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),Pr&&Jr(Pr,c)||(Pr=c,c=mo(uu,"onSelect"),0>=b,d-=b,wn=1<<32-wt(s)+d|a<pe?(be=ie,ie=null):be=ie.sibling;var Te=z(O,ie,L[pe],X);if(Te===null){ie===null&&(ie=be);break}t&&ie&&Te.alternate===null&&s(O,ie),M=p(Te,M,pe),_e===null?ae=Te:_e.sibling=Te,_e=Te,ie=be}if(pe===L.length)return a(O,ie),ve&&Dn(O,pe),ae;if(ie===null){for(;pepe?(be=ie,ie=null):be=ie.sibling;var Ci=z(O,ie,Te.value,X);if(Ci===null){ie===null&&(ie=be);break}t&&ie&&Ci.alternate===null&&s(O,ie),M=p(Ci,M,pe),_e===null?ae=Ci:_e.sibling=Ci,_e=Ci,ie=be}if(Te.done)return a(O,ie),ve&&Dn(O,pe),ae;if(ie===null){for(;!Te.done;pe++,Te=L.next())Te=F(O,Te.value,X),Te!==null&&(M=p(Te,M,pe),_e===null?ae=Te:_e.sibling=Te,_e=Te);return ve&&Dn(O,pe),ae}for(ie=c(ie);!Te.done;pe++,Te=L.next())Te=H(ie,O,pe,Te.value,X),Te!==null&&(t&&Te.alternate!==null&&ie.delete(Te.key===null?pe:Te.key),M=p(Te,M,pe),_e===null?ae=Te:_e.sibling=Te,_e=Te);return t&&ie.forEach(function(sx){return s(O,sx)}),ve&&Dn(O,pe),ae}function Oe(O,M,L,X){if(typeof L=="object"&&L!==null&&L.type===x&&L.key===null&&(L=L.props.children),typeof L=="object"&&L!==null){switch(L.$$typeof){case v:e:{for(var ae=L.key;M!==null;){if(M.key===ae){if(ae=L.type,ae===x){if(M.tag===7){a(O,M.sibling),X=d(M,L.props.children),X.return=O,O=X;break e}}else if(M.elementType===ae||typeof ae=="object"&&ae!==null&&ae.$$typeof===q&&Pi(ae)===M.type){a(O,M.sibling),X=d(M,L.props),ia(X,L),X.return=O,O=X;break e}a(O,M);break}else s(O,M);M=M.sibling}L.type===x?(X=Yi(L.props.children,O.mode,X,L.key),X.return=O,O=X):(X=jl(L.type,L.key,L.props,null,O.mode,X),ia(X,L),X.return=O,O=X)}return b(O);case E:e:{for(ae=L.key;M!==null;){if(M.key===ae)if(M.tag===4&&M.stateNode.containerInfo===L.containerInfo&&M.stateNode.implementation===L.implementation){a(O,M.sibling),X=d(M,L.children||[]),X.return=O,O=X;break e}else{a(O,M);break}else s(O,M);M=M.sibling}X=bu(L,O.mode,X),X.return=O,O=X}return b(O);case q:return L=Pi(L),Oe(O,M,L,X)}if(Ae(L))return te(O,M,L,X);if(V(L)){if(ae=V(L),typeof ae!="function")throw Error(r(150));return L=ae.call(L),oe(O,M,L,X)}if(typeof L.then=="function")return Oe(O,M,Hl(L),X);if(L.$$typeof===$)return Oe(O,M,Dl(O,L),X);ql(O,L)}return typeof L=="string"&&L!==""||typeof L=="number"||typeof L=="bigint"?(L=""+L,M!==null&&M.tag===6?(a(O,M.sibling),X=d(M,L),X.return=O,O=X):(a(O,M),X=yu(L,O.mode,X),X.return=O,O=X),b(O)):a(O,M)}return function(O,M,L,X){try{na=0;var ae=Oe(O,M,L,X);return Is=null,ae}catch(ie){if(ie===$s||ie===Bl)throw ie;var _e=$t(29,ie,null,O.mode);return _e.lanes=X,_e.return=O,_e}finally{}}}var Wi=Wp(!0),eg=Wp(!1),fi=!1;function Mu(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ou(t,s){t=t.updateQueue,s.updateQueue===t&&(s.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function hi(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function di(t,s,a){var c=t.updateQueue;if(c===null)return null;if(c=c.shared,(Ee&2)!==0){var d=c.pending;return d===null?s.next=s:(s.next=d.next,d.next=s),c.pending=s,s=Ol(t),Bp(t,null,a),s}return Ml(t,c,s,a),Ol(t)}function sa(t,s,a){if(s=s.updateQueue,s!==null&&(s=s.shared,(a&4194048)!==0)){var c=s.lanes;c&=t.pendingLanes,a|=c,s.lanes=a,Kd(t,a)}}function ju(t,s){var a=t.updateQueue,c=t.alternate;if(c!==null&&(c=c.updateQueue,a===c)){var d=null,p=null;if(a=a.firstBaseUpdate,a!==null){do{var b={lane:a.lane,tag:a.tag,payload:a.payload,callback:null,next:null};p===null?d=p=b:p=p.next=b,a=a.next}while(a!==null);p===null?d=p=s:p=p.next=s}else d=p=s;a={baseState:c.baseState,firstBaseUpdate:d,lastBaseUpdate:p,shared:c.shared,callbacks:c.callbacks},t.updateQueue=a;return}t=a.lastBaseUpdate,t===null?a.firstBaseUpdate=s:t.next=s,a.lastBaseUpdate=s}var Lu=!1;function ra(){if(Lu){var t=qs;if(t!==null)throw t}}function aa(t,s,a,c){Lu=!1;var d=t.updateQueue;fi=!1;var p=d.firstBaseUpdate,b=d.lastBaseUpdate,T=d.shared.pending;if(T!==null){d.shared.pending=null;var A=T,R=A.next;A.next=null,b===null?p=R:b.next=R,b=A;var G=t.alternate;G!==null&&(G=G.updateQueue,T=G.lastBaseUpdate,T!==b&&(T===null?G.firstBaseUpdate=R:T.next=R,G.lastBaseUpdate=A))}if(p!==null){var F=d.baseState;b=0,G=R=A=null,T=p;do{var z=T.lane&-536870913,H=z!==T.lane;if(H?(ye&z)===z:(c&z)===z){z!==0&&z===Hs&&(Lu=!0),G!==null&&(G=G.next={lane:0,tag:T.tag,payload:T.payload,callback:null,next:null});e:{var te=t,oe=T;z=s;var Oe=a;switch(oe.tag){case 1:if(te=oe.payload,typeof te=="function"){F=te.call(Oe,F,z);break e}F=te;break e;case 3:te.flags=te.flags&-65537|128;case 0:if(te=oe.payload,z=typeof te=="function"?te.call(Oe,F,z):te,z==null)break e;F=m({},F,z);break e;case 2:fi=!0}}z=T.callback,z!==null&&(t.flags|=64,H&&(t.flags|=8192),H=d.callbacks,H===null?d.callbacks=[z]:H.push(z))}else H={lane:z,tag:T.tag,payload:T.payload,callback:T.callback,next:null},G===null?(R=G=H,A=F):G=G.next=H,b|=z;if(T=T.next,T===null){if(T=d.shared.pending,T===null)break;H=T,T=H.next,H.next=null,d.lastBaseUpdate=H,d.shared.pending=null}}while(!0);G===null&&(A=F),d.baseState=A,d.firstBaseUpdate=R,d.lastBaseUpdate=G,p===null&&(d.shared.lanes=0),bi|=b,t.lanes=b,t.memoizedState=F}}function tg(t,s){if(typeof t!="function")throw Error(r(191,t));t.call(s)}function ng(t,s){var a=t.callbacks;if(a!==null)for(t.callbacks=null,t=0;tp?p:8;var b=B.T,T={};B.T=T,Zu(t,!1,s,a);try{var A=d(),R=B.S;if(R!==null&&R(T,A),A!==null&&typeof A=="object"&&typeof A.then=="function"){var G=X1(A,c);ca(t,s,G,Yt(t))}else ca(t,s,c,Yt(t))}catch(F){ca(t,s,{then:function(){},status:"rejected",reason:F},Yt())}finally{P.p=p,b!==null&&T.types!==null&&(b.types=T.types),B.T=b}}function W1(){}function Ju(t,s,a,c){if(t.tag!==5)throw Error(r(476));var d=Rg(t).queue;Lg(t,d,s,se,a===null?W1:function(){return Dg(t),a(c)})}function Rg(t){var s=t.memoizedState;if(s!==null)return s;s={memoizedState:se,baseState:se,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Hn,lastRenderedState:se},next:null};var a={};return s.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Hn,lastRenderedState:a},next:null},t.memoizedState=s,t=t.alternate,t!==null&&(t.memoizedState=s),s}function Dg(t){var s=Rg(t);s.next===null&&(s=t.alternate.memoizedState),ca(t,s.next.queue,{},Yt())}function Pu(){return ut(Aa)}function zg(){return Ye().memoizedState}function Bg(){return Ye().memoizedState}function ew(t){for(var s=t.return;s!==null;){switch(s.tag){case 24:case 3:var a=Yt();t=hi(a);var c=di(s,t,a);c!==null&&(Lt(c,s,a),sa(c,s,a)),s={cache:Au()},t.payload=s;return}s=s.return}}function tw(t,s,a){var c=Yt();a={lane:c,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Jl(t)?Hg(s,a):(a=gu(t,s,a,c),a!==null&&(Lt(a,t,c),qg(a,s,c)))}function Ug(t,s,a){var c=Yt();ca(t,s,a,c)}function ca(t,s,a,c){var d={lane:c,revertLane:0,gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null};if(Jl(t))Hg(s,d);else{var p=t.alternate;if(t.lanes===0&&(p===null||p.lanes===0)&&(p=s.lastRenderedReducer,p!==null))try{var b=s.lastRenderedState,T=p(b,a);if(d.hasEagerState=!0,d.eagerState=T,qt(T,b))return Ml(t,s,d,0),je===null&&kl(),!1}catch{}finally{}if(a=gu(t,s,d,c),a!==null)return Lt(a,t,c),qg(a,s,c),!0}return!1}function Zu(t,s,a,c){if(c={lane:2,revertLane:Of(),gesture:null,action:c,hasEagerState:!1,eagerState:null,next:null},Jl(t)){if(s)throw Error(r(479))}else s=gu(t,a,c,2),s!==null&&Lt(s,t,2)}function Jl(t){var s=t.alternate;return t===de||s!==null&&s===de}function Hg(t,s){Gs=Vl=!0;var a=t.pending;a===null?s.next=s:(s.next=a.next,a.next=s),t.pending=s}function qg(t,s,a){if((a&4194048)!==0){var c=s.lanes;c&=t.pendingLanes,a|=c,s.lanes=a,Kd(t,a)}}var ua={readContext:ut,use:Yl,useCallback:Ve,useContext:Ve,useEffect:Ve,useImperativeHandle:Ve,useLayoutEffect:Ve,useInsertionEffect:Ve,useMemo:Ve,useReducer:Ve,useRef:Ve,useState:Ve,useDebugValue:Ve,useDeferredValue:Ve,useTransition:Ve,useSyncExternalStore:Ve,useId:Ve,useHostTransitionStatus:Ve,useFormState:Ve,useActionState:Ve,useOptimistic:Ve,useMemoCache:Ve,useCacheRefresh:Ve};ua.useEffectEvent=Ve;var $g={readContext:ut,use:Yl,useCallback:function(t,s){return xt().memoizedState=[t,s===void 0?null:s],t},useContext:ut,useEffect:Tg,useImperativeHandle:function(t,s,a){a=a!=null?a.concat([t]):null,Fl(4194308,4,Cg.bind(null,s,t),a)},useLayoutEffect:function(t,s){return Fl(4194308,4,t,s)},useInsertionEffect:function(t,s){Fl(4,2,t,s)},useMemo:function(t,s){var a=xt();s=s===void 0?null:s;var c=t();if(es){vn(!0);try{t()}finally{vn(!1)}}return a.memoizedState=[c,s],c},useReducer:function(t,s,a){var c=xt();if(a!==void 0){var d=a(s);if(es){vn(!0);try{a(s)}finally{vn(!1)}}}else d=s;return c.memoizedState=c.baseState=d,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:d},c.queue=t,t=t.dispatch=tw.bind(null,de,t),[c.memoizedState,t]},useRef:function(t){var s=xt();return t={current:t},s.memoizedState=t},useState:function(t){t=Ku(t);var s=t.queue,a=Ug.bind(null,de,s);return s.dispatch=a,[t.memoizedState,a]},useDebugValue:Fu,useDeferredValue:function(t,s){var a=xt();return Qu(a,t,s)},useTransition:function(){var t=Ku(!1);return t=Lg.bind(null,de,t.queue,!0,!1),xt().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,s,a){var c=de,d=xt();if(ve){if(a===void 0)throw Error(r(407));a=a()}else{if(a=s(),je===null)throw Error(r(349));(ye&127)!==0||og(c,s,a)}d.memoizedState=a;var p={value:a,getSnapshot:s};return d.queue=p,Tg(ug.bind(null,c,p,t),[t]),c.flags|=2048,Ys(9,{destroy:void 0},cg.bind(null,c,p,a,s),null),a},useId:function(){var t=xt(),s=je.identifierPrefix;if(ve){var a=xn,c=wn;a=(c&~(1<<32-wt(c)-1)).toString(32)+a,s="_"+s+"R_"+a,a=Gl++,0<\/script>",p=p.removeChild(p.firstChild);break;case"select":p=typeof c.is=="string"?b.createElement("select",{is:c.is}):b.createElement("select"),c.multiple?p.multiple=!0:c.size&&(p.size=c.size);break;default:p=typeof c.is=="string"?b.createElement(d,{is:c.is}):b.createElement(d)}}p[ot]=s,p[Nt]=c;e:for(b=s.child;b!==null;){if(b.tag===5||b.tag===6)p.appendChild(b.stateNode);else if(b.tag!==4&&b.tag!==27&&b.child!==null){b.child.return=b,b=b.child;continue}if(b===s)break e;for(;b.sibling===null;){if(b.return===null||b.return===s)break e;b=b.return}b.sibling.return=b.return,b=b.sibling}s.stateNode=p;e:switch(ht(p,d,c),d){case"button":case"input":case"select":case"textarea":c=!!c.autoFocus;break e;case"img":c=!0;break e;default:c=!1}c&&$n(s)}}return ze(s),df(s,s.type,t===null?null:t.memoizedProps,s.pendingProps,a),null;case 6:if(t&&s.stateNode!=null)t.memoizedProps!==c&&$n(s);else{if(typeof c!="string"&&s.stateNode===null)throw Error(r(166));if(t=re.current,Bs(s)){if(t=s.stateNode,a=s.memoizedProps,c=null,d=ct,d!==null)switch(d.tag){case 27:case 5:c=d.memoizedProps}t[ot]=s,t=!!(t.nodeValue===a||c!==null&&c.suppressHydrationWarning===!0||ay(t.nodeValue,a)),t||ci(s,!0)}else t=yo(t).createTextNode(c),t[ot]=s,s.stateNode=t}return ze(s),null;case 31:if(a=s.memoizedState,t===null||t.memoizedState!==null){if(c=Bs(s),a!==null){if(t===null){if(!c)throw Error(r(318));if(t=s.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[ot]=s}else Xi(),(s.flags&128)===0&&(s.memoizedState=null),s.flags|=4;ze(s),t=!1}else a=xu(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=a),t=!0;if(!t)return s.flags&256?(Vt(s),s):(Vt(s),null);if((s.flags&128)!==0)throw Error(r(558))}return ze(s),null;case 13:if(c=s.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(d=Bs(s),c!==null&&c.dehydrated!==null){if(t===null){if(!d)throw Error(r(318));if(d=s.memoizedState,d=d!==null?d.dehydrated:null,!d)throw Error(r(317));d[ot]=s}else Xi(),(s.flags&128)===0&&(s.memoizedState=null),s.flags|=4;ze(s),d=!1}else d=xu(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=d),d=!0;if(!d)return s.flags&256?(Vt(s),s):(Vt(s),null)}return Vt(s),(s.flags&128)!==0?(s.lanes=a,s):(a=c!==null,t=t!==null&&t.memoizedState!==null,a&&(c=s.child,d=null,c.alternate!==null&&c.alternate.memoizedState!==null&&c.alternate.memoizedState.cachePool!==null&&(d=c.alternate.memoizedState.cachePool.pool),p=null,c.memoizedState!==null&&c.memoizedState.cachePool!==null&&(p=c.memoizedState.cachePool.pool),p!==d&&(c.flags|=2048)),a!==t&&a&&(s.child.flags|=8192),to(s,s.updateQueue),ze(s),null);case 4:return Re(),t===null&&Df(s.stateNode.containerInfo),ze(s),null;case 10:return Bn(s.type),ze(s),null;case 19:if(Y(Ke),c=s.memoizedState,c===null)return ze(s),null;if(d=(s.flags&128)!==0,p=c.rendering,p===null)if(d)ha(c,!1);else{if(Ge!==0||t!==null&&(t.flags&128)!==0)for(t=s.child;t!==null;){if(p=Il(t),p!==null){for(s.flags|=128,ha(c,!1),t=p.updateQueue,s.updateQueue=t,to(s,t),s.subtreeFlags=0,t=a,a=s.child;a!==null;)Up(a,t),a=a.sibling;return Z(Ke,Ke.current&1|2),ve&&Dn(s,c.treeForkCount),s.child}t=t.sibling}c.tail!==null&&St()>ao&&(s.flags|=128,d=!0,ha(c,!1),s.lanes=4194304)}else{if(!d)if(t=Il(p),t!==null){if(s.flags|=128,d=!0,t=t.updateQueue,s.updateQueue=t,to(s,t),ha(c,!0),c.tail===null&&c.tailMode==="hidden"&&!p.alternate&&!ve)return ze(s),null}else 2*St()-c.renderingStartTime>ao&&a!==536870912&&(s.flags|=128,d=!0,ha(c,!1),s.lanes=4194304);c.isBackwards?(p.sibling=s.child,s.child=p):(t=c.last,t!==null?t.sibling=p:s.child=p,c.last=p)}return c.tail!==null?(t=c.tail,c.rendering=t,c.tail=t.sibling,c.renderingStartTime=St(),t.sibling=null,a=Ke.current,Z(Ke,d?a&1|2:a&1),ve&&Dn(s,c.treeForkCount),t):(ze(s),null);case 22:case 23:return Vt(s),Du(),c=s.memoizedState!==null,t!==null?t.memoizedState!==null!==c&&(s.flags|=8192):c&&(s.flags|=8192),c?(a&536870912)!==0&&(s.flags&128)===0&&(ze(s),s.subtreeFlags&6&&(s.flags|=8192)):ze(s),a=s.updateQueue,a!==null&&to(s,a.retryQueue),a=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),c=null,s.memoizedState!==null&&s.memoizedState.cachePool!==null&&(c=s.memoizedState.cachePool.pool),c!==a&&(s.flags|=2048),t!==null&&Y(Ji),null;case 24:return a=null,t!==null&&(a=t.memoizedState.cache),s.memoizedState.cache!==a&&(s.flags|=2048),Bn(Je),ze(s),null;case 25:return null;case 30:return null}throw Error(r(156,s.tag))}function aw(t,s){switch(Su(s),s.tag){case 1:return t=s.flags,t&65536?(s.flags=t&-65537|128,s):null;case 3:return Bn(Je),Re(),t=s.flags,(t&65536)!==0&&(t&128)===0?(s.flags=t&-65537|128,s):null;case 26:case 27:case 5:return kn(s),null;case 31:if(s.memoizedState!==null){if(Vt(s),s.alternate===null)throw Error(r(340));Xi()}return t=s.flags,t&65536?(s.flags=t&-65537|128,s):null;case 13:if(Vt(s),t=s.memoizedState,t!==null&&t.dehydrated!==null){if(s.alternate===null)throw Error(r(340));Xi()}return t=s.flags,t&65536?(s.flags=t&-65537|128,s):null;case 19:return Y(Ke),null;case 4:return Re(),null;case 10:return Bn(s.type),null;case 22:case 23:return Vt(s),Du(),t!==null&&Y(Ji),t=s.flags,t&65536?(s.flags=t&-65537|128,s):null;case 24:return Bn(Je),null;case 25:return null;default:return null}}function fm(t,s){switch(Su(s),s.tag){case 3:Bn(Je),Re();break;case 26:case 27:case 5:kn(s);break;case 4:Re();break;case 31:s.memoizedState!==null&&Vt(s);break;case 13:Vt(s);break;case 19:Y(Ke);break;case 10:Bn(s.type);break;case 22:case 23:Vt(s),Du(),t!==null&&Y(Ji);break;case 24:Bn(Je)}}function da(t,s){try{var a=s.updateQueue,c=a!==null?a.lastEffect:null;if(c!==null){var d=c.next;a=d;do{if((a.tag&t)===t){c=void 0;var p=a.create,b=a.inst;c=p(),b.destroy=c}a=a.next}while(a!==d)}}catch(T){Ce(s,s.return,T)}}function mi(t,s,a){try{var c=s.updateQueue,d=c!==null?c.lastEffect:null;if(d!==null){var p=d.next;c=p;do{if((c.tag&t)===t){var b=c.inst,T=b.destroy;if(T!==void 0){b.destroy=void 0,d=s;var A=a,R=T;try{R()}catch(G){Ce(d,A,G)}}}c=c.next}while(c!==p)}}catch(G){Ce(s,s.return,G)}}function hm(t){var s=t.updateQueue;if(s!==null){var a=t.stateNode;try{ng(s,a)}catch(c){Ce(t,t.return,c)}}}function dm(t,s,a){a.props=ts(t.type,t.memoizedProps),a.state=t.memoizedState;try{a.componentWillUnmount()}catch(c){Ce(t,s,c)}}function pa(t,s){try{var a=t.ref;if(a!==null){switch(t.tag){case 26:case 27:case 5:var c=t.stateNode;break;case 30:c=t.stateNode;break;default:c=t.stateNode}typeof a=="function"?t.refCleanup=a(c):a.current=c}}catch(d){Ce(t,s,d)}}function _n(t,s){var a=t.ref,c=t.refCleanup;if(a!==null)if(typeof c=="function")try{c()}catch(d){Ce(t,s,d)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof a=="function")try{a(null)}catch(d){Ce(t,s,d)}else a.current=null}function pm(t){var s=t.type,a=t.memoizedProps,c=t.stateNode;try{e:switch(s){case"button":case"input":case"select":case"textarea":a.autoFocus&&c.focus();break e;case"img":a.src?c.src=a.src:a.srcSet&&(c.srcset=a.srcSet)}}catch(d){Ce(t,t.return,d)}}function pf(t,s,a){try{var c=t.stateNode;Cw(c,t.type,a,s),c[Nt]=s}catch(d){Ce(t,t.return,d)}}function gm(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&_i(t.type)||t.tag===4}function gf(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||gm(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&_i(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function mf(t,s,a){var c=t.tag;if(c===5||c===6)t=t.stateNode,s?(a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a).insertBefore(t,s):(s=a.nodeType===9?a.body:a.nodeName==="HTML"?a.ownerDocument.body:a,s.appendChild(t),a=a._reactRootContainer,a!=null||s.onclick!==null||(s.onclick=jn));else if(c!==4&&(c===27&&_i(t.type)&&(a=t.stateNode,s=null),t=t.child,t!==null))for(mf(t,s,a),t=t.sibling;t!==null;)mf(t,s,a),t=t.sibling}function no(t,s,a){var c=t.tag;if(c===5||c===6)t=t.stateNode,s?a.insertBefore(t,s):a.appendChild(t);else if(c!==4&&(c===27&&_i(t.type)&&(a=t.stateNode),t=t.child,t!==null))for(no(t,s,a),t=t.sibling;t!==null;)no(t,s,a),t=t.sibling}function mm(t){var s=t.stateNode,a=t.memoizedProps;try{for(var c=t.type,d=s.attributes;d.length;)s.removeAttributeNode(d[0]);ht(s,c,a),s[ot]=t,s[Nt]=a}catch(p){Ce(t,t.return,p)}}var In=!1,We=!1,yf=!1,ym=typeof WeakSet=="function"?WeakSet:Set,lt=null;function lw(t,s){if(t=t.containerInfo,Uf=To,t=kp(t),cu(t)){if("selectionStart"in t)var a={start:t.selectionStart,end:t.selectionEnd};else e:{a=(a=t.ownerDocument)&&a.defaultView||window;var c=a.getSelection&&a.getSelection();if(c&&c.rangeCount!==0){a=c.anchorNode;var d=c.anchorOffset,p=c.focusNode;c=c.focusOffset;try{a.nodeType,p.nodeType}catch{a=null;break e}var b=0,T=-1,A=-1,R=0,G=0,F=t,z=null;t:for(;;){for(var H;F!==a||d!==0&&F.nodeType!==3||(T=b+d),F!==p||c!==0&&F.nodeType!==3||(A=b+c),F.nodeType===3&&(b+=F.nodeValue.length),(H=F.firstChild)!==null;)z=F,F=H;for(;;){if(F===t)break t;if(z===a&&++R===d&&(T=b),z===p&&++G===c&&(A=b),(H=F.nextSibling)!==null)break;F=z,z=F.parentNode}F=H}a=T===-1||A===-1?null:{start:T,end:A}}else a=null}a=a||{start:0,end:0}}else a=null;for(Hf={focusedElem:t,selectionRange:a},To=!1,lt=s;lt!==null;)if(s=lt,t=s.child,(s.subtreeFlags&1028)!==0&&t!==null)t.return=s,lt=t;else for(;lt!==null;){switch(s=lt,p=s.alternate,t=s.flags,s.tag){case 0:if((t&4)!==0&&(t=s.updateQueue,t=t!==null?t.events:null,t!==null))for(a=0;a title"))),ht(p,c,a),p[ot]=t,at(p),c=p;break e;case"link":var b=_y("link","href",d).get(c+(a.href||""));if(b){for(var T=0;TOe&&(b=Oe,Oe=oe,oe=b);var O=Np(T,oe),M=Np(T,Oe);if(O&&M&&(H.rangeCount!==1||H.anchorNode!==O.node||H.anchorOffset!==O.offset||H.focusNode!==M.node||H.focusOffset!==M.offset)){var L=F.createRange();L.setStart(O.node,O.offset),H.removeAllRanges(),oe>Oe?(H.addRange(L),H.extend(M.node,M.offset)):(L.setEnd(M.node,M.offset),H.addRange(L))}}}}for(F=[],H=T;H=H.parentNode;)H.nodeType===1&&F.push({element:H,left:H.scrollLeft,top:H.scrollTop});for(typeof T.focus=="function"&&T.focus(),T=0;Ta?32:a,B.T=null,a=Tf,Tf=null;var p=Si,b=Xn;if(it=0,Ps=Si=null,Xn=0,(Ee&6)!==0)throw Error(r(331));var T=Ee;if(Ee|=4,Cm(p.current),Em(p,p.current,b,a),Ee=T,Sa(0,!1),rt&&typeof rt.onPostCommitFiberRoot=="function")try{rt.onPostCommitFiberRoot(Mn,p)}catch{}return!0}finally{P.p=d,B.T=c,Ym(t,s)}}function Fm(t,s,a){s=Zt(a,s),s=nf(t.stateNode,s,2),t=di(t,s,2),t!==null&&(qr(t,2),Tn(t))}function Ce(t,s,a){if(t.tag===3)Fm(t,t,a);else for(;s!==null;){if(s.tag===3){Fm(s,t,a);break}else if(s.tag===1){var c=s.stateNode;if(typeof s.type.getDerivedStateFromError=="function"||typeof c.componentDidCatch=="function"&&(vi===null||!vi.has(c))){t=Zt(a,t),a=Qg(2),c=di(s,a,2),c!==null&&(Jg(a,c,s,t),qr(c,2),Tn(c));break}}s=s.return}}function Cf(t,s,a){var c=t.pingCache;if(c===null){c=t.pingCache=new uw;var d=new Set;c.set(s,d)}else d=c.get(s),d===void 0&&(d=new Set,c.set(s,d));d.has(a)||(Sf=!0,d.add(a),t=gw.bind(null,t,s,a),s.then(t,t))}function gw(t,s,a){var c=t.pingCache;c!==null&&c.delete(s),t.pingedLanes|=t.suspendedLanes&a,t.warmLanes&=~a,je===t&&(ye&a)===a&&(Ge===4||Ge===3&&(ye&62914560)===ye&&300>St()-ro?(Ee&2)===0&&Zs(t,0):wf|=a,Js===ye&&(Js=0)),Tn(t)}function Qm(t,s){s===0&&(s=Vd()),t=Ki(t,s),t!==null&&(qr(t,s),Tn(t))}function mw(t){var s=t.memoizedState,a=0;s!==null&&(a=s.retryLane),Qm(t,a)}function yw(t,s){var a=0;switch(t.tag){case 31:case 13:var c=t.stateNode,d=t.memoizedState;d!==null&&(a=d.retryLane);break;case 19:c=t.stateNode;break;case 22:c=t.stateNode._retryCache;break;default:throw Error(r(314))}c!==null&&c.delete(s),Qm(t,a)}function bw(t,s){return ni(t,s)}var ho=null,er=null,kf=!1,po=!1,Mf=!1,xi=0;function Tn(t){t!==er&&t.next===null&&(er===null?ho=er=t:er=er.next=t),po=!0,kf||(kf=!0,Sw())}function Sa(t,s){if(!Mf&&po){Mf=!0;do for(var a=!1,c=ho;c!==null;){if(t!==0){var d=c.pendingLanes;if(d===0)var p=0;else{var b=c.suspendedLanes,T=c.pingedLanes;p=(1<<31-wt(42|t)+1)-1,p&=d&~(b&~T),p=p&201326741?p&201326741|1:p?p|2:0}p!==0&&(a=!0,Wm(c,p))}else p=ye,p=yl(c,c===je?p:0,c.cancelPendingCommit!==null||c.timeoutHandle!==-1),(p&3)===0||Hr(c,p)||(a=!0,Wm(c,p));c=c.next}while(a);Mf=!1}}function vw(){Jm()}function Jm(){po=kf=!1;var t=0;xi!==0&&Mw()&&(t=xi);for(var s=St(),a=null,c=ho;c!==null;){var d=c.next,p=Pm(c,s);p===0?(c.next=null,a===null?ho=d:a.next=d,d===null&&(er=a)):(a=c,(t!==0||(p&3)!==0)&&(po=!0)),c=d}it!==0&&it!==5||Sa(t),xi!==0&&(xi=0)}function Pm(t,s){for(var a=t.suspendedLanes,c=t.pingedLanes,d=t.expirationTimes,p=t.pendingLanes&-62914561;0T)break;var G=A.transferSize,F=A.initiatorType;G&&ly(F)&&(A=A.responseEnd,b+=G*(A"u"?null:document;function vy(t,s,a){var c=tr;if(c&&typeof s=="string"&&s){var d=Jt(s);d='link[rel="'+t+'"][href="'+d+'"]',typeof a=="string"&&(d+='[crossorigin="'+a+'"]'),by.has(d)||(by.add(d),t={rel:t,crossOrigin:a,href:s},c.querySelector(d)===null&&(s=c.createElement("link"),ht(s,"link",t),at(s),c.head.appendChild(s)))}}function Hw(t){Fn.D(t),vy("dns-prefetch",t,null)}function qw(t,s){Fn.C(t,s),vy("preconnect",t,s)}function $w(t,s,a){Fn.L(t,s,a);var c=tr;if(c&&t&&s){var d='link[rel="preload"][as="'+Jt(s)+'"]';s==="image"&&a&&a.imageSrcSet?(d+='[imagesrcset="'+Jt(a.imageSrcSet)+'"]',typeof a.imageSizes=="string"&&(d+='[imagesizes="'+Jt(a.imageSizes)+'"]')):d+='[href="'+Jt(t)+'"]';var p=d;switch(s){case"style":p=nr(t);break;case"script":p=ir(t)}rn.has(p)||(t=m({rel:"preload",href:s==="image"&&a&&a.imageSrcSet?void 0:t,as:s},a),rn.set(p,t),c.querySelector(d)!==null||s==="style"&&c.querySelector(Ta(p))||s==="script"&&c.querySelector(Ea(p))||(s=c.createElement("link"),ht(s,"link",t),at(s),c.head.appendChild(s)))}}function Iw(t,s){Fn.m(t,s);var a=tr;if(a&&t){var c=s&&typeof s.as=="string"?s.as:"script",d='link[rel="modulepreload"][as="'+Jt(c)+'"][href="'+Jt(t)+'"]',p=d;switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":p=ir(t)}if(!rn.has(p)&&(t=m({rel:"modulepreload",href:t},s),rn.set(p,t),a.querySelector(d)===null)){switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(a.querySelector(Ea(p)))return}c=a.createElement("link"),ht(c,"link",t),at(c),a.head.appendChild(c)}}}function Vw(t,s,a){Fn.S(t,s,a);var c=tr;if(c&&t){var d=Ts(c).hoistableStyles,p=nr(t);s=s||"default";var b=d.get(p);if(!b){var T={loading:0,preload:null};if(b=c.querySelector(Ta(p)))T.loading=5;else{t=m({rel:"stylesheet",href:t,"data-precedence":s},a),(a=rn.get(p))&&Yf(t,a);var A=b=c.createElement("link");at(A),ht(A,"link",t),A._p=new Promise(function(R,G){A.onload=R,A.onerror=G}),A.addEventListener("load",function(){T.loading|=1}),A.addEventListener("error",function(){T.loading|=2}),T.loading|=4,vo(b,s,c)}b={type:"stylesheet",instance:b,count:1,state:T},d.set(p,b)}}}function Gw(t,s){Fn.X(t,s);var a=tr;if(a&&t){var c=Ts(a).hoistableScripts,d=ir(t),p=c.get(d);p||(p=a.querySelector(Ea(d)),p||(t=m({src:t,async:!0},s),(s=rn.get(d))&&Xf(t,s),p=a.createElement("script"),at(p),ht(p,"link",t),a.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},c.set(d,p))}}function Kw(t,s){Fn.M(t,s);var a=tr;if(a&&t){var c=Ts(a).hoistableScripts,d=ir(t),p=c.get(d);p||(p=a.querySelector(Ea(d)),p||(t=m({src:t,async:!0,type:"module"},s),(s=rn.get(d))&&Xf(t,s),p=a.createElement("script"),at(p),ht(p,"link",t),a.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},c.set(d,p))}}function Sy(t,s,a,c){var d=(d=re.current)?bo(d):null;if(!d)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof a.precedence=="string"&&typeof a.href=="string"?(s=nr(a.href),a=Ts(d).hoistableStyles,c=a.get(s),c||(c={type:"style",instance:null,count:0,state:null},a.set(s,c)),c):{type:"void",instance:null,count:0,state:null};case"link":if(a.rel==="stylesheet"&&typeof a.href=="string"&&typeof a.precedence=="string"){t=nr(a.href);var p=Ts(d).hoistableStyles,b=p.get(t);if(b||(d=d.ownerDocument||d,b={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},p.set(t,b),(p=d.querySelector(Ta(t)))&&!p._p&&(b.instance=p,b.state.loading=5),rn.has(t)||(a={rel:"preload",as:"style",href:a.href,crossOrigin:a.crossOrigin,integrity:a.integrity,media:a.media,hrefLang:a.hrefLang,referrerPolicy:a.referrerPolicy},rn.set(t,a),p||Yw(d,t,a,b.state))),s&&c===null)throw Error(r(528,""));return b}if(s&&c!==null)throw Error(r(529,""));return null;case"script":return s=a.async,a=a.src,typeof a=="string"&&s&&typeof s!="function"&&typeof s!="symbol"?(s=ir(a),a=Ts(d).hoistableScripts,c=a.get(s),c||(c={type:"script",instance:null,count:0,state:null},a.set(s,c)),c):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function nr(t){return'href="'+Jt(t)+'"'}function Ta(t){return'link[rel="stylesheet"]['+t+"]"}function wy(t){return m({},t,{"data-precedence":t.precedence,precedence:null})}function Yw(t,s,a,c){t.querySelector('link[rel="preload"][as="style"]['+s+"]")?c.loading=1:(s=t.createElement("link"),c.preload=s,s.addEventListener("load",function(){return c.loading|=1}),s.addEventListener("error",function(){return c.loading|=2}),ht(s,"link",a),at(s),t.head.appendChild(s))}function ir(t){return'[src="'+Jt(t)+'"]'}function Ea(t){return"script[async]"+t}function xy(t,s,a){if(s.count++,s.instance===null)switch(s.type){case"style":var c=t.querySelector('style[data-href~="'+Jt(a.href)+'"]');if(c)return s.instance=c,at(c),c;var d=m({},a,{"data-href":a.href,"data-precedence":a.precedence,href:null,precedence:null});return c=(t.ownerDocument||t).createElement("style"),at(c),ht(c,"style",d),vo(c,a.precedence,t),s.instance=c;case"stylesheet":d=nr(a.href);var p=t.querySelector(Ta(d));if(p)return s.state.loading|=4,s.instance=p,at(p),p;c=wy(a),(d=rn.get(d))&&Yf(c,d),p=(t.ownerDocument||t).createElement("link"),at(p);var b=p;return b._p=new Promise(function(T,A){b.onload=T,b.onerror=A}),ht(p,"link",c),s.state.loading|=4,vo(p,a.precedence,t),s.instance=p;case"script":return p=ir(a.src),(d=t.querySelector(Ea(p)))?(s.instance=d,at(d),d):(c=a,(d=rn.get(p))&&(c=m({},a),Xf(c,d)),t=t.ownerDocument||t,d=t.createElement("script"),at(d),ht(d,"link",c),t.head.appendChild(d),s.instance=d);case"void":return null;default:throw Error(r(443,s.type))}else s.type==="stylesheet"&&(s.state.loading&4)===0&&(c=s.instance,s.state.loading|=4,vo(c,a.precedence,t));return s.instance}function vo(t,s,a){for(var c=a.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),d=c.length?c[c.length-1]:null,p=d,b=0;b title"):null)}function Xw(t,s,a){if(a===1||s.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof s.precedence!="string"||typeof s.href!="string"||s.href==="")break;return!0;case"link":if(typeof s.rel!="string"||typeof s.href!="string"||s.href===""||s.onLoad||s.onError)break;switch(s.rel){case"stylesheet":return t=s.disabled,typeof s.precedence=="string"&&t==null;default:return!0}case"script":if(s.async&&typeof s.async!="function"&&typeof s.async!="symbol"&&!s.onLoad&&!s.onError&&s.src&&typeof s.src=="string")return!0}return!1}function Ey(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function Fw(t,s,a,c){if(a.type==="stylesheet"&&(typeof c.media!="string"||matchMedia(c.media).matches!==!1)&&(a.state.loading&4)===0){if(a.instance===null){var d=nr(c.href),p=s.querySelector(Ta(d));if(p){s=p._p,s!==null&&typeof s=="object"&&typeof s.then=="function"&&(t.count++,t=wo.bind(t),s.then(t,t)),a.state.loading|=4,a.instance=p,at(p);return}p=s.ownerDocument||s,c=wy(c),(d=rn.get(d))&&Yf(c,d),p=p.createElement("link"),at(p);var b=p;b._p=new Promise(function(T,A){b.onload=T,b.onerror=A}),ht(p,"link",c),a.instance=p}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(a,s),(s=a.state.preload)&&(a.state.loading&3)===0&&(t.count++,a=wo.bind(t),s.addEventListener("load",a),s.addEventListener("error",a))}}var Ff=0;function Qw(t,s){return t.stylesheets&&t.count===0&&_o(t,t.stylesheets),0Ff?50:800)+s);return t.unsuspend=a,function(){t.unsuspend=null,clearTimeout(c),clearTimeout(d)}}:null}function wo(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)_o(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var xo=null;function _o(t,s){t.stylesheets=null,t.unsuspend!==null&&(t.count++,xo=new Map,s.forEach(Jw,t),xo=null,wo.call(t))}function Jw(t,s){if(!(s.state.loading&4)){var a=xo.get(t);if(a)var c=a.get(null);else{a=new Map,xo.set(t,a);for(var d=t.querySelectorAll("link[data-precedence],style[data-precedence]"),p=0;p"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}return n(),ih.exports=_x(),ih.exports}var zC=Tx();const Qh=new Map([["APIRequestContext.fetch",{title:'{method} "{url}"'}],["APIRequestContext.fetchResponseBody",{title:"Get response body",group:"getter"}],["APIRequestContext.fetchLog",{internal:!0}],["APIRequestContext.storageState",{title:"Get storage state"}],["APIRequestContext.disposeAPIResponse",{internal:!0}],["APIRequestContext.dispose",{internal:!0}],["LocalUtils.zip",{internal:!0}],["LocalUtils.harOpen",{internal:!0}],["LocalUtils.harLookup",{internal:!0}],["LocalUtils.harClose",{internal:!0}],["LocalUtils.harUnzip",{internal:!0}],["LocalUtils.connect",{internal:!0}],["LocalUtils.tracingStarted",{internal:!0}],["LocalUtils.addStackToTracingNoReply",{internal:!0}],["LocalUtils.traceDiscarded",{internal:!0}],["LocalUtils.globToRegex",{internal:!0}],["Root.initialize",{internal:!0}],["Playwright.newRequest",{title:"Create request context"}],["DebugController.initialize",{internal:!0}],["DebugController.setReportStateChanged",{internal:!0}],["DebugController.setRecorderMode",{internal:!0}],["DebugController.highlight",{internal:!0}],["DebugController.hideHighlight",{internal:!0}],["DebugController.resume",{internal:!0}],["DebugController.kill",{internal:!0}],["SocksSupport.socksConnected",{internal:!0}],["SocksSupport.socksFailed",{internal:!0}],["SocksSupport.socksData",{internal:!0}],["SocksSupport.socksError",{internal:!0}],["SocksSupport.socksEnd",{internal:!0}],["BrowserType.launch",{title:"Launch browser"}],["BrowserType.launchPersistentContext",{title:"Launch persistent context"}],["BrowserType.connectOverCDP",{title:"Connect over CDP"}],["Browser.close",{title:"Close browser",pausesBeforeAction:!0}],["Browser.killForTests",{internal:!0}],["Browser.defaultUserAgentForTest",{internal:!0}],["Browser.newContext",{title:"Create context"}],["Browser.newContextForReuse",{internal:!0}],["Browser.disconnectFromReusedContext",{internal:!0}],["Browser.newBrowserCDPSession",{title:"Create CDP session",group:"configuration"}],["Browser.startTracing",{title:"Start browser tracing",group:"configuration"}],["Browser.stopTracing",{title:"Stop browser tracing",group:"configuration"}],["EventTarget.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["BrowserContext.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["Page.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["Worker.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["WebSocket.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["ElectronApplication.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["AndroidDevice.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["PageAgent.waitForEventInfo",{title:'Wait for event "{info.event}"',snapshot:!0}],["BrowserContext.addCookies",{title:"Add cookies",group:"configuration"}],["BrowserContext.addInitScript",{title:"Add init script",group:"configuration"}],["BrowserContext.clearCookies",{title:"Clear cookies",group:"configuration"}],["BrowserContext.clearPermissions",{title:"Clear permissions",group:"configuration"}],["BrowserContext.close",{title:"Close context",pausesBeforeAction:!0}],["BrowserContext.cookies",{title:"Get cookies",group:"getter"}],["BrowserContext.exposeBinding",{title:"Expose binding",group:"configuration"}],["BrowserContext.grantPermissions",{title:"Grant permissions",group:"configuration"}],["BrowserContext.newPage",{title:"Create page"}],["BrowserContext.registerSelectorEngine",{internal:!0}],["BrowserContext.setTestIdAttributeName",{internal:!0}],["BrowserContext.setExtraHTTPHeaders",{title:"Set extra HTTP headers",group:"configuration"}],["BrowserContext.setGeolocation",{title:"Set geolocation",group:"configuration"}],["BrowserContext.setHTTPCredentials",{title:"Set HTTP credentials",group:"configuration"}],["BrowserContext.setNetworkInterceptionPatterns",{title:"Route requests",group:"route"}],["BrowserContext.setWebSocketInterceptionPatterns",{title:"Route WebSockets",group:"route"}],["BrowserContext.setOffline",{title:"Set offline mode"}],["BrowserContext.storageState",{title:"Get storage state"}],["BrowserContext.pause",{title:"Pause"}],["BrowserContext.enableRecorder",{internal:!0}],["BrowserContext.disableRecorder",{internal:!0}],["BrowserContext.exposeConsoleApi",{internal:!0}],["BrowserContext.newCDPSession",{title:"Create CDP session",group:"configuration"}],["BrowserContext.harStart",{internal:!0}],["BrowserContext.harExport",{internal:!0}],["BrowserContext.createTempFiles",{internal:!0}],["BrowserContext.updateSubscription",{internal:!0}],["BrowserContext.clockFastForward",{title:'Fast forward clock "{ticksNumber|ticksString}"'}],["BrowserContext.clockInstall",{title:'Install clock "{timeNumber|timeString}"'}],["BrowserContext.clockPauseAt",{title:'Pause clock "{timeNumber|timeString}"'}],["BrowserContext.clockResume",{title:"Resume clock"}],["BrowserContext.clockRunFor",{title:'Run clock "{ticksNumber|ticksString}"'}],["BrowserContext.clockSetFixedTime",{title:'Set fixed time "{timeNumber|timeString}"'}],["BrowserContext.clockSetSystemTime",{title:'Set system time "{timeNumber|timeString}"'}],["Page.addInitScript",{title:"Add init script",group:"configuration"}],["Page.close",{title:"Close page",pausesBeforeAction:!0}],["Page.consoleMessages",{title:"Get console messages",group:"getter"}],["Page.emulateMedia",{title:"Emulate media",snapshot:!0,pausesBeforeAction:!0}],["Page.exposeBinding",{title:"Expose binding",group:"configuration"}],["Page.goBack",{title:"Go back",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.goForward",{title:"Go forward",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.requestGC",{title:"Request garbage collection",group:"configuration"}],["Page.registerLocatorHandler",{title:"Register locator handler"}],["Page.resolveLocatorHandlerNoReply",{internal:!0}],["Page.unregisterLocatorHandler",{title:"Unregister locator handler"}],["Page.reload",{title:"Reload",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.expectScreenshot",{title:"Expect screenshot",snapshot:!0,pausesBeforeAction:!0}],["Page.screenshot",{title:"Screenshot",snapshot:!0,pausesBeforeAction:!0}],["Page.setExtraHTTPHeaders",{title:"Set extra HTTP headers",group:"configuration"}],["Page.setNetworkInterceptionPatterns",{title:"Route requests",group:"route"}],["Page.setWebSocketInterceptionPatterns",{title:"Route WebSockets",group:"route"}],["Page.setViewportSize",{title:"Set viewport size",snapshot:!0,pausesBeforeAction:!0}],["Page.keyboardDown",{title:'Key down "{key}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.keyboardUp",{title:'Key up "{key}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.keyboardInsertText",{title:'Insert "{text}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.keyboardType",{title:'Type "{text}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.keyboardPress",{title:'Press "{key}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.mouseMove",{title:"Mouse move",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.mouseDown",{title:"Mouse down",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.mouseUp",{title:"Mouse up",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.mouseClick",{title:"Click",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.mouseWheel",{title:"Mouse wheel",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.touchscreenTap",{title:"Tap",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Page.pageErrors",{title:"Get page errors",group:"getter"}],["Page.pdf",{title:"PDF"}],["Page.requests",{title:"Get network requests",group:"getter"}],["Page.snapshotForAI",{internal:!0}],["Page.startJSCoverage",{title:"Start JS coverage",group:"configuration"}],["Page.stopJSCoverage",{title:"Stop JS coverage",group:"configuration"}],["Page.startCSSCoverage",{title:"Start CSS coverage",group:"configuration"}],["Page.stopCSSCoverage",{title:"Stop CSS coverage",group:"configuration"}],["Page.bringToFront",{title:"Bring to front"}],["Page.updateSubscription",{internal:!0}],["Page.agent",{internal:!0}],["Frame.evalOnSelector",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["Frame.evalOnSelectorAll",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["Frame.addScriptTag",{title:"Add script tag",snapshot:!0,pausesBeforeAction:!0}],["Frame.addStyleTag",{title:"Add style tag",snapshot:!0,pausesBeforeAction:!0}],["Frame.ariaSnapshot",{title:"Aria snapshot",snapshot:!0,pausesBeforeAction:!0}],["Frame.blur",{title:"Blur",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Frame.check",{title:"Check",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.click",{title:"Click",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.content",{title:"Get content",snapshot:!0,pausesBeforeAction:!0}],["Frame.dragAndDrop",{title:"Drag and drop",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.dblclick",{title:"Double click",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.dispatchEvent",{title:'Dispatch "{type}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Frame.evaluateExpression",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["Frame.evaluateExpressionHandle",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["Frame.fill",{title:'Fill "{value}"',slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.focus",{title:"Focus",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Frame.frameElement",{title:"Get frame element",group:"getter"}],["Frame.resolveSelector",{internal:!0}],["Frame.highlight",{title:"Highlight element",group:"configuration"}],["Frame.getAttribute",{title:'Get attribute "{name}"',snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.goto",{title:'Navigate to "{url}"',slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["Frame.hover",{title:"Hover",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.innerHTML",{title:"Get HTML",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.innerText",{title:"Get inner text",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.inputValue",{title:"Get input value",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.isChecked",{title:"Is checked",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.isDisabled",{title:"Is disabled",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.isEnabled",{title:"Is enabled",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.isHidden",{title:"Is hidden",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.isVisible",{title:"Is visible",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.isEditable",{title:"Is editable",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.press",{title:'Press "{key}"',slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.querySelector",{title:"Query selector",snapshot:!0}],["Frame.querySelectorAll",{title:"Query selector all",snapshot:!0}],["Frame.queryCount",{title:"Query count",snapshot:!0,pausesBeforeAction:!0}],["Frame.selectOption",{title:"Select option",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.setContent",{title:"Set content",snapshot:!0,pausesBeforeAction:!0}],["Frame.setInputFiles",{title:"Set input files",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.tap",{title:"Tap",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.textContent",{title:"Get text content",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["Frame.title",{title:"Get page title",group:"getter"}],["Frame.type",{title:'Type "{text}"',slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.uncheck",{title:"Uncheck",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["Frame.waitForTimeout",{title:"Wait for timeout",snapshot:!0}],["Frame.waitForFunction",{title:"Wait for function",snapshot:!0,pausesBeforeAction:!0}],["Frame.waitForSelector",{title:"Wait for selector",snapshot:!0}],["Frame.expect",{title:'Expect "{expression}"',snapshot:!0,pausesBeforeAction:!0}],["Worker.evaluateExpression",{title:"Evaluate"}],["Worker.evaluateExpressionHandle",{title:"Evaluate"}],["Worker.updateSubscription",{internal:!0}],["JSHandle.dispose",{internal:!0}],["ElementHandle.dispose",{internal:!0}],["JSHandle.evaluateExpression",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.evaluateExpression",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["JSHandle.evaluateExpressionHandle",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.evaluateExpressionHandle",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["JSHandle.getPropertyList",{title:"Get property list",group:"getter"}],["ElementHandle.getPropertyList",{title:"Get property list",group:"getter"}],["JSHandle.getProperty",{title:"Get JS property",group:"getter"}],["ElementHandle.getProperty",{title:"Get JS property",group:"getter"}],["JSHandle.jsonValue",{title:"Get JSON value",group:"getter"}],["ElementHandle.jsonValue",{title:"Get JSON value",group:"getter"}],["ElementHandle.evalOnSelector",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.evalOnSelectorAll",{title:"Evaluate",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.boundingBox",{title:"Get bounding box",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.check",{title:"Check",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.click",{title:"Click",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.contentFrame",{title:"Get content frame",group:"getter"}],["ElementHandle.dblclick",{title:"Double click",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.dispatchEvent",{title:"Dispatch event",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.fill",{title:'Fill "{value}"',slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.focus",{title:"Focus",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.getAttribute",{title:"Get attribute",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.hover",{title:"Hover",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.innerHTML",{title:"Get HTML",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.innerText",{title:"Get inner text",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.inputValue",{title:"Get input value",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.isChecked",{title:"Is checked",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.isDisabled",{title:"Is disabled",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.isEditable",{title:"Is editable",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.isEnabled",{title:"Is enabled",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.isHidden",{title:"Is hidden",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.isVisible",{title:"Is visible",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.ownerFrame",{title:"Get owner frame",group:"getter"}],["ElementHandle.press",{title:'Press "{key}"',slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.querySelector",{title:"Query selector",snapshot:!0}],["ElementHandle.querySelectorAll",{title:"Query selector all",snapshot:!0}],["ElementHandle.screenshot",{title:"Screenshot",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.scrollIntoViewIfNeeded",{title:"Scroll into view",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.selectOption",{title:"Select option",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.selectText",{title:"Select text",slowMo:!0,snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.setInputFiles",{title:"Set input files",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.tap",{title:"Tap",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.textContent",{title:"Get text content",snapshot:!0,pausesBeforeAction:!0,group:"getter"}],["ElementHandle.type",{title:"Type",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.uncheck",{title:"Uncheck",slowMo:!0,snapshot:!0,pausesBeforeInput:!0}],["ElementHandle.waitForElementState",{title:"Wait for state",snapshot:!0,pausesBeforeAction:!0}],["ElementHandle.waitForSelector",{title:"Wait for selector",snapshot:!0}],["Request.response",{internal:!0}],["Request.rawRequestHeaders",{internal:!0}],["Route.redirectNavigationRequest",{internal:!0}],["Route.abort",{title:"Abort request",group:"route"}],["Route.continue",{title:"Continue request",group:"route"}],["Route.fulfill",{title:"Fulfill request",group:"route"}],["WebSocketRoute.connect",{title:"Connect WebSocket to server",group:"route"}],["WebSocketRoute.ensureOpened",{internal:!0}],["WebSocketRoute.sendToPage",{title:"Send WebSocket message",group:"route"}],["WebSocketRoute.sendToServer",{title:"Send WebSocket message",group:"route"}],["WebSocketRoute.closePage",{internal:!0}],["WebSocketRoute.closeServer",{internal:!0}],["Response.body",{title:"Get response body",group:"getter"}],["Response.securityDetails",{internal:!0}],["Response.serverAddr",{internal:!0}],["Response.rawResponseHeaders",{internal:!0}],["Response.sizes",{internal:!0}],["BindingCall.reject",{internal:!0}],["BindingCall.resolve",{internal:!0}],["Dialog.accept",{title:"Accept dialog"}],["Dialog.dismiss",{title:"Dismiss dialog"}],["Tracing.tracingStart",{title:"Start tracing",group:"configuration"}],["Tracing.tracingStartChunk",{title:"Start tracing",group:"configuration"}],["Tracing.tracingGroup",{title:'Trace "{name}"'}],["Tracing.tracingGroupEnd",{title:"Group end"}],["Tracing.tracingStopChunk",{title:"Stop tracing",group:"configuration"}],["Tracing.tracingStop",{title:"Stop tracing",group:"configuration"}],["Artifact.pathAfterFinished",{internal:!0}],["Artifact.saveAs",{internal:!0}],["Artifact.saveAsStream",{internal:!0}],["Artifact.failure",{internal:!0}],["Artifact.stream",{internal:!0}],["Artifact.cancel",{internal:!0}],["Artifact.delete",{internal:!0}],["Stream.read",{internal:!0}],["Stream.close",{internal:!0}],["WritableStream.write",{internal:!0}],["WritableStream.close",{internal:!0}],["CDPSession.send",{title:"Send CDP command",group:"configuration"}],["CDPSession.detach",{title:"Detach CDP session",group:"configuration"}],["Electron.launch",{title:"Launch electron"}],["ElectronApplication.browserWindow",{internal:!0}],["ElectronApplication.evaluateExpression",{title:"Evaluate"}],["ElectronApplication.evaluateExpressionHandle",{title:"Evaluate"}],["ElectronApplication.updateSubscription",{internal:!0}],["Android.devices",{internal:!0}],["AndroidSocket.write",{internal:!0}],["AndroidSocket.close",{internal:!0}],["AndroidDevice.wait",{title:"Wait"}],["AndroidDevice.fill",{title:'Fill "{text}"'}],["AndroidDevice.tap",{title:"Tap"}],["AndroidDevice.drag",{title:"Drag"}],["AndroidDevice.fling",{title:"Fling"}],["AndroidDevice.longTap",{title:"Long tap"}],["AndroidDevice.pinchClose",{title:"Pinch close"}],["AndroidDevice.pinchOpen",{title:"Pinch open"}],["AndroidDevice.scroll",{title:"Scroll"}],["AndroidDevice.swipe",{title:"Swipe"}],["AndroidDevice.info",{internal:!0}],["AndroidDevice.screenshot",{title:"Screenshot"}],["AndroidDevice.inputType",{title:"Type"}],["AndroidDevice.inputPress",{title:"Press"}],["AndroidDevice.inputTap",{title:"Tap"}],["AndroidDevice.inputSwipe",{title:"Swipe"}],["AndroidDevice.inputDrag",{title:"Drag"}],["AndroidDevice.launchBrowser",{title:"Launch browser"}],["AndroidDevice.open",{title:"Open app"}],["AndroidDevice.shell",{title:"Execute shell command",group:"configuration"}],["AndroidDevice.installApk",{title:"Install apk"}],["AndroidDevice.push",{title:"Push"}],["AndroidDevice.connectToWebView",{title:"Connect to Web View"}],["AndroidDevice.close",{internal:!0}],["JsonPipe.send",{internal:!0}],["JsonPipe.close",{internal:!0}],["PageAgent.perform",{title:'Perform "{task}"'}],["PageAgent.expect",{title:'Expect "{expectation}"'}],["PageAgent.extract",{title:'Extract "{query}"'}],["PageAgent.dispose",{internal:!0}],["PageAgent.usage",{title:"Get agent usage",group:"configuration"}]]);function eb(n,e){var i;return(i=Ex(n,e))==null?void 0:i.replaceAll(` -`,"\\n")}function Ex(n,e){if(n)for(const i of e.split("|")){if(i==="url")try{const l=new URL(n[i]);return l.protocol==="data:"?l.protocol:l.protocol==="about:"?n[i]:l.pathname+l.search}catch{if(n[i]!==void 0)return n[i]}if(i==="timeNumber"&&n[i]!==void 0)return new Date(n[i]).toString();const r=Ax(n,i);if(r!==void 0)return r}}function Ax(n,e){const i=e.split(".");let r=n;for(const l of i){if(typeof r!="object"||r===null)return;r=r[l]}if(r!==void 0)return String(r)}function Nx(n){var i;return(n.title??((i=Qh.get(n.type+"."+n.method))==null?void 0:i.title)??n.method).replace(/\{([^}]+)\}/g,(r,l)=>eb(n.params,l)??r)}function Cx(n){var e;return(e=Qh.get(n.type+"."+n.method))==null?void 0:e.group}const qa=Symbol("context"),tb=Symbol("nextInContext"),nb=Symbol("prevByEndTime"),ib=Symbol("nextByStartTime"),Py=Symbol("events");class BC{constructor(e,i){var l;i.forEach(o=>kx(o));const r=i.find(o=>o.origin==="library");this.traceUri=e,this.browserName=(r==null?void 0:r.browserName)||"",this.sdkLanguage=r==null?void 0:r.sdkLanguage,this.channel=r==null?void 0:r.channel,this.testIdAttributeName=r==null?void 0:r.testIdAttributeName,this.platform=(r==null?void 0:r.platform)||"",this.playwrightVersion=(l=i.find(o=>o.playwrightVersion))==null?void 0:l.playwrightVersion,this.title=(r==null?void 0:r.title)||"",this.options=(r==null?void 0:r.options)||{},this.actions=Mx(i),this.pages=[].concat(...i.map(o=>o.pages)),this.wallTime=i.map(o=>o.wallTime).reduce((o,u)=>Math.min(o||Number.MAX_VALUE,u),Number.MAX_VALUE),this.startTime=i.map(o=>o.startTime).reduce((o,u)=>Math.min(o,u),Number.MAX_VALUE),this.endTime=i.map(o=>o.endTime).reduce((o,u)=>Math.max(o,u),Number.MIN_VALUE),this.events=[].concat(...i.map(o=>o.events)),this.stdio=[].concat(...i.map(o=>o.stdio)),this.errors=[].concat(...i.map(o=>o.errors)),this.hasSource=i.some(o=>o.hasSource),this.hasStepData=i.some(o=>o.origin==="testRunner"),this.resources=[...i.map(o=>o.resources)].flat(),this.attachments=this.actions.flatMap(o=>{var u;return((u=o.attachments)==null?void 0:u.map(f=>({...f,callId:o.callId,traceUri:e})))??[]}),this.visibleAttachments=this.attachments.filter(o=>!o.name.startsWith("_")),this.events.sort((o,u)=>o.time-u.time),this.resources.sort((o,u)=>o._monotonicTime-u._monotonicTime),this.errorDescriptors=this.hasStepData?this._errorDescriptorsFromTestRunner():this._errorDescriptorsFromActions(),this.sources=Bx(this.actions,this.errorDescriptors),this.actionCounters=new Map;for(const o of this.actions)o.group=o.group??Cx({type:o.class,method:o.method}),o.group&&this.actionCounters.set(o.group,1+(this.actionCounters.get(o.group)||0))}createRelativeUrl(e){const i=new URL("http://localhost/"+e);return i.searchParams.set("trace",this.traceUri),i.toString().substring(17)}failedAction(){return this.actions.findLast(e=>e.error)}filteredActions(e){const i=new Set(e);return this.actions.filter(r=>!r.group||i.has(r.group))}renderActionTree(e){const i=this.filteredActions(e??[]),{rootItem:r}=sb(i),l=[],o=(u,f)=>{const h=Nx({...u.action,type:u.action.class});l.push(`${f}${h||u.id}`);for(const g of u.children)o(g,f+" ")};return r.children.forEach(u=>o(u,"")),l}_errorDescriptorsFromActions(){var i;const e=[];for(const r of this.actions||[])(i=r.error)!=null&&i.message&&e.push({action:r,stack:r.stack,message:r.error.message});return e}_errorDescriptorsFromTestRunner(){return this.errors.filter(e=>!!e.message).map((e,i)=>({stack:e.stack,message:e.message}))}}function kx(n){for(const i of n.pages)i[qa]=n;for(let i=0;i=0;i--){const r=n.actions[i];r[tb]=e,r.class!=="Route"&&(e=r)}for(const i of n.events)i[qa]=n;for(const i of n.resources)i[qa]=n}function Mx(n){const e=[],i=Ox(n);e.push(...i),e.sort((r,l)=>l.parentId===r.callId?1:r.parentId===l.callId?-1:r.endTime-l.endTime);for(let r=1;rl.parentId===r.callId?-1:r.parentId===l.callId?1:r.startTime-l.startTime);for(let r=0;r+1u.origin==="library"),r=n.filter(u=>u.origin==="testRunner");if(!r.length||!i.length)return n.map(u=>u.actions.map(f=>({...f,context:u}))).flat();for(const u of i)for(const f of u.actions)e.set(f.stepId||`tmp-step@${++Zy}`,{...f,context:u});const l=Lx(r,e);l&&jx(i,l);const o=new Map;for(const u of r)for(const f of u.actions){const h=f.stepId&&e.get(f.stepId);if(h){o.set(f.callId,h.callId),f.error&&(h.error=f.error),f.attachments&&(h.attachments=f.attachments),f.annotations&&(h.annotations=f.annotations),f.parentId&&(h.parentId=o.get(f.parentId)??f.parentId),f.group&&(h.group=f.group),h.startTime=f.startTime,h.endTime=f.endTime;continue}f.parentId&&(f.parentId=o.get(f.parentId)??f.parentId),e.set(f.stepId||`tmp-step@${++Zy}`,{...f,context:u})}return[...e.values()]}function jx(n,e){for(const i of n){i.startTime+=e,i.endTime+=e;for(const r of i.actions)r.startTime&&(r.startTime+=e),r.endTime&&(r.endTime+=e);for(const r of i.events)r.time+=e;for(const r of i.stdio)r.timestamp+=e;for(const r of i.pages)for(const l of r.screencastFrames)l.timestamp+=e;for(const r of i.resources)r._monotonicTime&&(r._monotonicTime+=e)}}function Lx(n,e){for(const i of n)for(const r of i.actions){if(!r.startTime)continue;const l=r.stepId?e.get(r.stepId):void 0;if(l)return r.startTime-l.startTime}return 0}function sb(n){const e=new Map;for(const l of n)e.set(l.callId,{id:l.callId,parent:void 0,children:[],action:l});const i={action:{...Ux},id:"",parent:void 0,children:[]};for(const l of e.values()){i.action.startTime=Math.min(i.action.startTime,l.action.startTime),i.action.endTime=Math.max(i.action.endTime,l.action.endTime);const o=l.action.parentId&&e.get(l.action.parentId)||i;o.children.push(l),l.parent=o}const r=l=>{for(const o of l.children)o.action.stack=o.action.stack??l.action.stack,r(o)};return r(i),{rootItem:i,itemMap:e}}function rb(n){return n[qa]}function Rx(n){return n[tb]}function Wy(n){return n[nb]}function e0(n){return n[ib]}function Dx(n){let e=0,i=0;for(const r of zx(n)){if(r.type==="console"){const l=r.messageType;l==="warning"?++i:l==="error"&&++e}r.type==="event"&&r.method==="pageError"&&++e}return{errors:e,warnings:i}}function zx(n){let e=n[Py];if(e)return e;const i=Rx(n);return e=rb(n).events.filter(r=>r.time>=n.startTime&&(!i||r.time{const h=Math.max(l,n)*window.devicePixelRatio,[g,y]=on(o?o+"."+r+":size":void 0,h),[m,w]=on(o?o+"."+r+":size":void 0,h),[v,E]=U.useState(null),[x,_]=gs();let N;r==="vertical"?(N=m/window.devicePixelRatio,x&&x.heightE({offset:r==="vertical"?$.clientY:$.clientX,size:N}),onMouseUp:()=>E(null),onMouseMove:$=>{if(!$.buttons)E(null);else if(v){const D=(r==="vertical"?$.clientY:$.clientX)-v.offset,K=i?v.size+D:v.size-D,q=$.target.parentElement.getBoundingClientRect(),j=Math.min(Math.max(l,K),(r==="vertical"?q.height:q.width)-l);r==="vertical"?w(j*window.devicePixelRatio):y(j*window.devicePixelRatio)}}})]})},et=function(n,e,i){return n>=e&&n<=i};function Rt(n){return et(n,48,57)}function t0(n){return Rt(n)||et(n,65,70)||et(n,97,102)}function qx(n){return et(n,65,90)}function $x(n){return et(n,97,122)}function Ix(n){return qx(n)||$x(n)}function Vx(n){return n>=128}function $o(n){return Ix(n)||Vx(n)||n===95}function n0(n){return $o(n)||Rt(n)||n===45}function Gx(n){return et(n,0,8)||n===11||et(n,14,31)||n===127}function Io(n){return n===10}function Qn(n){return Io(n)||n===9||n===32}const Kx=1114111;class Jh extends Error{constructor(e){super(e),this.name="InvalidCharacterError"}}function Yx(n){const e=[];for(let i=0;i=e.length?-1:e[V]},u=function(V){if(V===void 0&&(V=1),V>3)throw"Spec Error: no more than three codepoints of lookahead.";return o(i+V)},f=function(V){return V===void 0&&(V=1),i+=V,l=o(i),!0},h=function(){return i-=1,!0},g=function(V){return V===void 0&&(V=l),V===-1},y=function(){if(m(),f(),Qn(l)){for(;Qn(u());)f();return new ic}else{if(l===34)return E();if(l===35)if(n0(u())||N(u(1),u(2))){const V=new vb("");return $(u(1),u(2),u(3))&&(V.type="id"),V.value=Q(),V}else return new dt(l);else return l===36?u()===61?(f(),new Jx):new dt(l):l===39?E():l===40?new mb:l===41?new Ph:l===42?u()===61?(f(),new Px):new dt(l):l===43?K()?(h(),w()):new dt(l):l===44?new hb:l===45?K()?(h(),w()):u(1)===45&&u(2)===62?(f(2),new cb):I()?(h(),v()):new dt(l):l===46?K()?(h(),w()):new dt(l):l===58?new ub:l===59?new fb:l===60?u(1)===33&&u(2)===45&&u(3)===45?(f(3),new ob):new dt(l):l===64?$(u(1),u(2),u(3))?new bb(Q()):new dt(l):l===91?new gb:l===92?C()?(h(),v()):new dt(l):l===93?new kh:l===94?u()===61?(f(),new Qx):new dt(l):l===123?new db:l===124?u()===61?(f(),new Fx):u()===124?(f(),new yb):new dt(l):l===125?new pb:l===126?u()===61?(f(),new Xx):new dt(l):Rt(l)?(h(),w()):$o(l)?(h(),v()):g()?new Go:new dt(l)}},m=function(){for(;u(1)===47&&u(2)===42;)for(f(2);;)if(f(),l===42&&u()===47){f();break}else if(g())return},w=function(){const V=q();if($(u(1),u(2),u(3))){const J=new Zx;return J.value=V.value,J.repr=V.repr,J.type=V.type,J.unit=Q(),J}else if(u()===37){f();const J=new xb;return J.value=V.value,J.repr=V.repr,J}else{const J=new wb;return J.value=V.value,J.repr=V.repr,J.type=V.type,J}},v=function(){const V=Q();if(V.toLowerCase()==="url"&&u()===40){for(f();Qn(u(1))&&Qn(u(2));)f();return u()===34||u()===39?new Ya(V):Qn(u())&&(u(2)===34||u(2)===39)?new Ya(V):x()}else return u()===40?(f(),new Ya(V)):new Zh(V)},E=function(V){V===void 0&&(V=l);let J="";for(;f();){if(l===V||g())return new Wh(J);if(Io(l))return h(),new lb;l===92?g(u())||(Io(u())?f():J+=st(_())):J+=st(l)}throw new Error("Internal error")},x=function(){const V=new Sb("");for(;Qn(u());)f();if(g(u()))return V;for(;f();){if(l===41||g())return V;if(Qn(l)){for(;Qn(u());)f();return u()===41||g(u())?(f(),V):(ne(),new Vo)}else{if(l===34||l===39||l===40||Gx(l))return ne(),new Vo;if(l===92)if(C())V.value+=st(_());else return ne(),new Vo;else V.value+=st(l)}}throw new Error("Internal error")},_=function(){if(f(),t0(l)){const V=[l];for(let W=0;W<5&&t0(u());W++)f(),V.push(l);Qn(u())&&f();let J=parseInt(V.map(function(W){return String.fromCharCode(W)}).join(""),16);return J>Kx&&(J=65533),J}else return g()?65533:l},N=function(V,J){return!(V!==92||Io(J))},C=function(){return N(l,u())},$=function(V,J,W){return V===45?$o(J)||J===45||N(J,W):$o(V)?!0:V===92?N(V,J):!1},I=function(){return $(l,u(1),u(2))},D=function(V,J,W){return V===43||V===45?!!(Rt(J)||J===46&&Rt(W)):V===46?!!Rt(J):!!Rt(V)},K=function(){return D(l,u(1),u(2))},Q=function(){let V="";for(;f();)if(n0(l))V+=st(l);else if(C())V+=st(_());else return h(),V;throw new Error("Internal parse error")},q=function(){let V="",J="integer";for((u()===43||u()===45)&&(f(),V+=st(l));Rt(u());)f(),V+=st(l);if(u(1)===46&&Rt(u(2)))for(f(),V+=st(l),f(),V+=st(l),J="number";Rt(u());)f(),V+=st(l);const W=u(1),Ae=u(2),B=u(3);if((W===69||W===101)&&Rt(Ae))for(f(),V+=st(l),f(),V+=st(l),J="number";Rt(u());)f(),V+=st(l);else if((W===69||W===101)&&(Ae===43||Ae===45)&&Rt(B))for(f(),V+=st(l),f(),V+=st(l),f(),V+=st(l),J="number";Rt(u());)f(),V+=st(l);const P=j(V);return{type:J,value:P,repr:V}},j=function(V){return+V},ne=function(){for(;f();){if(l===41||g())return;C()&&_()}};let le=0;for(;!g(u());)if(r.push(y()),le++,le>e.length*2)throw new Error("I'm infinite-looping!");return r}class Qe{constructor(){this.tokenType=""}toJSON(){return{token:this.tokenType}}toString(){return this.tokenType}toSource(){return""+this}}class lb extends Qe{constructor(){super(...arguments),this.tokenType="BADSTRING"}}class Vo extends Qe{constructor(){super(...arguments),this.tokenType="BADURL"}}class ic extends Qe{constructor(){super(...arguments),this.tokenType="WHITESPACE"}toString(){return"WS"}toSource(){return" "}}class ob extends Qe{constructor(){super(...arguments),this.tokenType="CDO"}toSource(){return""}}class ub extends Qe{constructor(){super(...arguments),this.tokenType=":"}}class fb extends Qe{constructor(){super(...arguments),this.tokenType=";"}}class hb extends Qe{constructor(){super(...arguments),this.tokenType=","}}class Er extends Qe{constructor(){super(...arguments),this.value="",this.mirror=""}}class db extends Er{constructor(){super(),this.tokenType="{",this.value="{",this.mirror="}"}}class pb extends Er{constructor(){super(),this.tokenType="}",this.value="}",this.mirror="{"}}class gb extends Er{constructor(){super(),this.tokenType="[",this.value="[",this.mirror="]"}}class kh extends Er{constructor(){super(),this.tokenType="]",this.value="]",this.mirror="["}}class mb extends Er{constructor(){super(),this.tokenType="(",this.value="(",this.mirror=")"}}class Ph extends Er{constructor(){super(),this.tokenType=")",this.value=")",this.mirror="("}}class Xx extends Qe{constructor(){super(...arguments),this.tokenType="~="}}class Fx extends Qe{constructor(){super(...arguments),this.tokenType="|="}}class Qx extends Qe{constructor(){super(...arguments),this.tokenType="^="}}class Jx extends Qe{constructor(){super(...arguments),this.tokenType="$="}}class Px extends Qe{constructor(){super(...arguments),this.tokenType="*="}}class yb extends Qe{constructor(){super(...arguments),this.tokenType="||"}}class Go extends Qe{constructor(){super(...arguments),this.tokenType="EOF"}toSource(){return""}}class dt extends Qe{constructor(e){super(),this.tokenType="DELIM",this.value="",this.value=st(e)}toString(){return"DELIM("+this.value+")"}toJSON(){const e=this.constructor.prototype.constructor.prototype.toJSON.call(this);return e.value=this.value,e}toSource(){return this.value==="\\"?`\\ -`:this.value}}class Ar extends Qe{constructor(){super(...arguments),this.value=""}ASCIIMatch(e){return this.value.toLowerCase()===e.toLowerCase()}toJSON(){const e=this.constructor.prototype.constructor.prototype.toJSON.call(this);return e.value=this.value,e}}class Zh extends Ar{constructor(e){super(),this.tokenType="IDENT",this.value=e}toString(){return"IDENT("+this.value+")"}toSource(){return ol(this.value)}}class Ya extends Ar{constructor(e){super(),this.tokenType="FUNCTION",this.value=e,this.mirror=")"}toString(){return"FUNCTION("+this.value+")"}toSource(){return ol(this.value)+"("}}class bb extends Ar{constructor(e){super(),this.tokenType="AT-KEYWORD",this.value=e}toString(){return"AT("+this.value+")"}toSource(){return"@"+ol(this.value)}}class vb extends Ar{constructor(e){super(),this.tokenType="HASH",this.value=e,this.type="unrestricted"}toString(){return"HASH("+this.value+")"}toJSON(){const e=this.constructor.prototype.constructor.prototype.toJSON.call(this);return e.value=this.value,e.type=this.type,e}toSource(){return this.type==="id"?"#"+ol(this.value):"#"+Wx(this.value)}}class Wh extends Ar{constructor(e){super(),this.tokenType="STRING",this.value=e}toString(){return'"'+_b(this.value)+'"'}}class Sb extends Ar{constructor(e){super(),this.tokenType="URL",this.value=e}toString(){return"URL("+this.value+")"}toSource(){return'url("'+_b(this.value)+'")'}}class wb extends Qe{constructor(){super(),this.tokenType="NUMBER",this.type="integer",this.repr=""}toString(){return this.type==="integer"?"INT("+this.value+")":"NUMBER("+this.value+")"}toJSON(){const e=super.toJSON();return e.value=this.value,e.type=this.type,e.repr=this.repr,e}toSource(){return this.repr}}class xb extends Qe{constructor(){super(),this.tokenType="PERCENTAGE",this.repr=""}toString(){return"PERCENTAGE("+this.value+")"}toJSON(){const e=this.constructor.prototype.constructor.prototype.toJSON.call(this);return e.value=this.value,e.repr=this.repr,e}toSource(){return this.repr+"%"}}class Zx extends Qe{constructor(){super(),this.tokenType="DIMENSION",this.type="integer",this.repr="",this.unit=""}toString(){return"DIM("+this.value+","+this.unit+")"}toJSON(){const e=this.constructor.prototype.constructor.prototype.toJSON.call(this);return e.value=this.value,e.type=this.type,e.repr=this.repr,e.unit=this.unit,e}toSource(){const e=this.repr;let i=ol(this.unit);return i[0].toLowerCase()==="e"&&(i[1]==="-"||et(i.charCodeAt(1),48,57))&&(i="\\65 "+i.slice(1,i.length)),e+i}}function ol(n){n=""+n;let e="";const i=n.charCodeAt(0);for(let r=0;r=128||l===45||l===95||et(l,48,57)||et(l,65,90)||et(l,97,122)?e+=n[r]:e+="\\"+n[r]}return e}function Wx(n){n=""+n;let e="";for(let i=0;i=128||r===45||r===95||et(r,48,57)||et(r,65,90)||et(r,97,122)?e+=n[i]:e+="\\"+r.toString(16)+" "}return e}function _b(n){n=""+n;let e="";for(let i=0;ij instanceof bb||j instanceof lb||j instanceof Vo||j instanceof yb||j instanceof ob||j instanceof cb||j instanceof fb||j instanceof db||j instanceof pb||j instanceof Sb||j instanceof xb);if(r)throw new Dt(`Unsupported token "${r.toSource()}" while parsing css selector "${n}". Did you mean to CSS.escape it?`);let l=0;const o=new Set;function u(){return new Dt(`Unexpected token "${i[l].toSource()}" while parsing css selector "${n}". Did you mean to CSS.escape it?`)}function f(){for(;i[l]instanceof ic;)l++}function h(j=l){return i[j]instanceof Zh}function g(j=l){return i[j]instanceof Wh}function y(j=l){return i[j]instanceof wb}function m(j=l){return i[j]instanceof hb}function w(j=l){return i[j]instanceof mb}function v(j=l){return i[j]instanceof Ph}function E(j=l){return i[j]instanceof Ya}function x(j=l){return i[j]instanceof dt&&i[j].value==="*"}function _(j=l){return i[j]instanceof Go}function N(j=l){return i[j]instanceof dt&&[">","+","~"].includes(i[j].value)}function C(j=l){return m(j)||v(j)||_(j)||N(j)||i[j]instanceof ic}function $(){const j=[I()];for(;f(),!!m();)l++,j.push(I());return j}function I(){return f(),y()||g()?i[l++].value:D()}function D(){const j={simples:[]};for(f(),N()?j.simples.push({selector:{functions:[{name:"scope",args:[]}]},combinator:""}):j.simples.push({selector:K(),combinator:""});;){if(f(),N())j.simples[j.simples.length-1].combinator=i[l++].value,f();else if(C())break;j.simples.push({combinator:"",selector:K()})}return j}function K(){let j="";const ne=[];for(;!C();)if(h()||x())j+=i[l++].toSource();else if(i[l]instanceof vb)j+=i[l++].toSource();else if(i[l]instanceof dt&&i[l].value===".")if(l++,h())j+="."+i[l++].toSource();else throw u();else if(i[l]instanceof ub)if(l++,h())if(!e.has(i[l].value.toLowerCase()))j+=":"+i[l++].toSource();else{const le=i[l++].value.toLowerCase();ne.push({name:le,args:[]}),o.add(le)}else if(E()){const le=i[l++].value.toLowerCase();if(e.has(le)?(ne.push({name:le,args:$()}),o.add(le)):j+=`:${le}(${Q()})`,f(),!v())throw u();l++}else throw u();else if(i[l]instanceof gb){for(j+="[",l++;!(i[l]instanceof kh)&&!_();)j+=i[l++].toSource();if(!(i[l]instanceof kh))throw u();j+="]",l++}else throw u();if(!j&&!ne.length)throw u();return{css:j||void 0,functions:ne}}function Q(){let j="",ne=1;for(;!_()&&((w()||E())&&ne++,v()&&ne--,!!ne);)j+=i[l++].toSource();return j}const q=$();if(!_())throw u();if(q.some(j=>typeof j!="object"||!("simples"in j)))throw new Dt(`Error while parsing css selector "${n}". Did you mean to CSS.escape it?`);return{selector:q,names:Array.from(o)}}const Mh=new Set(["internal:has","internal:has-not","internal:and","internal:or","internal:chain","left-of","right-of","above","below","near"]),t_=new Set(["left-of","right-of","above","below","near"]),Tb=new Set(["not","is","where","has","scope","light","visible","text","text-matches","text-is","has-text","above","below","right-of","left-of","near","nth-match"]);function cl(n){const e=s_(n),i=[];for(const r of e.parts){if(r.name==="css"||r.name==="css:light"){r.name==="css:light"&&(r.body=":light("+r.body+")");const l=e_(r.body,Tb);i.push({name:"css",body:l.selector,source:r.body});continue}if(Mh.has(r.name)){let l,o;try{const g=JSON.parse("["+r.body+"]");if(!Array.isArray(g)||g.length<1||g.length>2||typeof g[0]!="string")throw new Dt(`Malformed selector: ${r.name}=`+r.body);if(l=g[0],g.length===2){if(typeof g[1]!="number"||!t_.has(r.name))throw new Dt(`Malformed selector: ${r.name}=`+r.body);o=g[1]}}catch{throw new Dt(`Malformed selector: ${r.name}=`+r.body)}const u={name:r.name,source:r.body,body:{parsed:cl(l),distance:o}},f=[...u.body.parsed.parts].reverse().find(g=>g.name==="internal:control"&&g.body==="enter-frame"),h=f?u.body.parsed.parts.indexOf(f):-1;h!==-1&&n_(u.body.parsed.parts.slice(0,h+1),i.slice(0,h+1))&&u.body.parsed.parts.splice(0,h+1),i.push(u);continue}i.push({...r,source:r.body})}if(Mh.has(i[0].name))throw new Dt(`"${i[0].name}" selector cannot be first`);return{capture:e.capture,parts:i}}function n_(n,e){return An({parts:n})===An({parts:e})}function An(n,e){return typeof n=="string"?n:n.parts.map((i,r)=>{let l=!0;!e&&r!==n.capture&&(i.name==="css"||i.name==="xpath"&&i.source.startsWith("//")||i.source.startsWith(".."))&&(l=!1);const o=l?i.name+"=":"";return`${r===n.capture?"*":""}${o}${i.source}`}).join(" >> ")}function i_(n,e){const i=(r,l)=>{for(const o of r.parts)e(o,l),Mh.has(o.name)&&i(o.body.parsed,!0)};i(n,!1)}function s_(n){let e=0,i,r=0;const l={parts:[]},o=()=>{const f=n.substring(r,e).trim(),h=f.indexOf("=");let g,y;h!==-1&&f.substring(0,h).trim().match(/^[a-zA-Z_0-9-+:*]+$/)?(g=f.substring(0,h).trim(),y=f.substring(h+1)):f.length>1&&f[0]==='"'&&f[f.length-1]==='"'||f.length>1&&f[0]==="'"&&f[f.length-1]==="'"?(g="text",y=f):/^\(*\/\//.test(f)||f.startsWith("..")?(g="xpath",y=f):(g="css",y=f);let m=!1;if(g[0]==="*"&&(m=!0,g=g.substring(1)),l.parts.push({name:g,body:y}),m){if(l.capture!==void 0)throw new Dt("Only one of the selectors can capture using * modifier");l.capture=l.parts.length-1}};if(!n.includes(">>"))return e=n.length,o(),l;const u=()=>{const h=n.substring(r,e).match(/^\s*text\s*=(.*)$/);return!!h&&!!h[1]};for(;e"&&n[e+1]===">"?(o(),e+=2,r=e):e++}return o(),l}function ds(n,e){let i=0,r=n.length===0;const l=()=>n[i]||"",o=()=>{const _=l();return++i,r=i>=n.length,_},u=_=>{throw r?new Dt(`Unexpected end of selector while parsing selector \`${n}\``):new Dt(`Error while parsing selector \`${n}\` - unexpected symbol "${l()}" at position ${i}`+(_?" during "+_:""))};function f(){for(;!r&&/\s/.test(l());)o()}function h(_){return _>="€"||_>="0"&&_<="9"||_>="A"&&_<="Z"||_>="a"&&_<="z"||_>="0"&&_<="9"||_==="_"||_==="-"}function g(){let _="";for(f();!r&&h(l());)_+=o();return _}function y(_){let N=o();for(N!==_&&u("parsing quoted string");!r&&l()!==_;)l()==="\\"&&o(),N+=o();return l()!==_&&u("parsing quoted string"),N+=o(),N}function m(){o()!=="/"&&u("parsing regular expression");let _="",N=!1;for(;!r;){if(l()==="\\")_+=o(),r&&u("parsing regular expression");else if(N&&l()==="]")N=!1;else if(!N&&l()==="[")N=!0;else if(!N&&l()==="/")break;_+=o()}o()!=="/"&&u("parsing regular expression");let C="";for(;!r&&l().match(/[dgimsuy]/);)C+=o();try{return new RegExp(_,C)}catch($){throw new Dt(`Error while parsing selector \`${n}\`: ${$.message}`)}}function w(){let _="";return f(),l()==="'"||l()==='"'?_=y(l()).slice(1,-1):_=g(),_||u("parsing property path"),_}function v(){f();let _="";return r||(_+=o()),!r&&_!=="="&&(_+=o()),["=","*=","^=","$=","|=","~="].includes(_)||u("parsing operator"),_}function E(){o();const _=[];for(_.push(w()),f();l()===".";)o(),_.push(w()),f();if(l()==="]")return o(),{name:_.join("."),jsonPath:_,op:"",value:null,caseSensitive:!1};const N=v();let C,$=!0;if(f(),l()==="/"){if(N!=="=")throw new Dt(`Error while parsing selector \`${n}\` - cannot use ${N} in attribute with regular expression`);C=m()}else if(l()==="'"||l()==='"')C=y(l()).slice(1,-1),f(),l()==="i"||l()==="I"?($=!1,o()):(l()==="s"||l()==="S")&&($=!0,o());else{for(C="";!r&&(h(l())||l()==="+"||l()===".");)C+=o();C==="true"?C=!0:C==="false"?C=!1:e||(C=+C,Number.isNaN(C)&&u("parsing attribute value"))}if(f(),l()!=="]"&&u("parsing attribute value"),o(),N!=="="&&typeof C!="string")throw new Dt(`Error while parsing selector \`${n}\` - cannot use ${N} in attribute with non-string matching value - ${C}`);return{name:_.join("."),jsonPath:_,op:N,value:C,caseSensitive:$}}const x={name:"",attributes:[]};for(x.name=g(),f();l()==="[";)x.attributes.push(E()),f();if(r||u(void 0),!x.name&&!x.attributes.length)throw new Dt(`Error while parsing selector \`${n}\` - selector cannot be empty`);return x}function gc(n,e="'"){const i=JSON.stringify(n),r=i.substring(1,i.length-1).replace(/\\"/g,'"');if(e==="'")return e+r.replace(/[']/g,"\\'")+e;if(e==='"')return e+r.replace(/["]/g,'\\"')+e;if(e==="`")return e+r.replace(/[`]/g,"\\`")+e;throw new Error("Invalid escape char")}function sc(n){return n.charAt(0).toUpperCase()+n.substring(1)}function Eb(n){return n.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}function fr(n){return`"${n.replace(/["\\]/g,e=>"\\"+e)}"`}let ss;function r_(){ss=new Map}function At(n){let e=ss==null?void 0:ss.get(n);return e===void 0&&(e=n.replace(/[\u200b\u00ad]/g,"").trim().replace(/\s+/g," "),ss==null||ss.set(n,e)),e}function mc(n){return n.replace(/(^|[^\\])(\\\\)*\\(['"`])/g,"$1$2$3")}function Ab(n){return n.unicode||n.unicodeSets?String(n):String(n).replace(/(^|[^\\])(\\\\)*(["'`])/g,"$1$2\\$3").replace(/>>/g,"\\>\\>")}function zt(n,e){return typeof n!="string"?Ab(n):`${JSON.stringify(n)}${e?"s":"i"}`}function Tt(n,e){return typeof n!="string"?Ab(n):`"${n.replace(/\\/g,"\\\\").replace(/["]/g,'\\"')}"${e?"s":"i"}`}function a_(n,e,i=""){if(n.length<=e)return n;const r=[...n];return r.length>e?r.slice(0,e-i.length).join("")+i:r.join("")}function i0(n,e){return a_(n,e,"…")}function rc(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function l_(n,e){const i=n.length,r=e.length;let l=0,o=0;const u=Array(i+1).fill(null).map(()=>Array(r+1).fill(0));for(let f=1;f<=i;f++)for(let h=1;h<=r;h++)n[f-1]===e[h-1]&&(u[f][h]=u[f-1][h-1]+1,u[f][h]>l&&(l=u[f][h],o=f));return n.slice(o-l,o)}function o_(n,e){try{const i=cl(e),r=c_(i);return r||os(new Cb[n],i,!1,1)[0]}catch{return e}}function c_(n){const e=n.parts[n.parts.length-1];if((e==null?void 0:e.name)==="internal:describe"){const i=JSON.parse(e.body);if(typeof i=="string")return i}}function Oi(n,e,i=!1){return Nb(n,e,i,1)[0]}function Nb(n,e,i=!1,r=20,l){try{return os(new Cb[n](l),cl(e),i,r)}catch{return[e]}}function os(n,e,i=!1,r=20){const l=[...e.parts],o=[];let u=i?"frame-locator":"page";for(let f=0;fn.generateLocator(g,"has",x)));continue}if(h.name==="internal:has-not"){const E=os(n,h.body.parsed,!1,r);o.push(E.map(x=>n.generateLocator(g,"hasNot",x)));continue}if(h.name==="internal:and"){const E=os(n,h.body.parsed,!1,r);o.push(E.map(x=>n.generateLocator(g,"and",x)));continue}if(h.name==="internal:or"){const E=os(n,h.body.parsed,!1,r);o.push(E.map(x=>n.generateLocator(g,"or",x)));continue}if(h.name==="internal:chain"){const E=os(n,h.body.parsed,!1,r);o.push(E.map(x=>n.generateLocator(g,"chain",x)));continue}if(h.name==="internal:label"){const{exact:E,text:x}=La(h.body);o.push([n.generateLocator(g,"label",x,{exact:E})]);continue}if(h.name==="internal:role"){const E=ds(h.body,!0),x={attrs:[]};for(const _ of E.attributes)_.name==="name"?(x.exact=_.caseSensitive,x.name=_.value):(_.name==="level"&&typeof _.value=="string"&&(_.value=+_.value),x.attrs.push({name:_.name==="include-hidden"?"includeHidden":_.name,value:_.value}));o.push([n.generateLocator(g,"role",E.name,x)]);continue}if(h.name==="internal:testid"){const E=ds(h.body,!0),{value:x}=E.attributes[0];o.push([n.generateLocator(g,"test-id",x)]);continue}if(h.name==="internal:attr"){const E=ds(h.body,!0),{name:x,value:_,caseSensitive:N}=E.attributes[0],C=_,$=!!N;if(x==="placeholder"){o.push([n.generateLocator(g,"placeholder",C,{exact:$})]);continue}if(x==="alt"){o.push([n.generateLocator(g,"alt",C,{exact:$})]);continue}if(x==="title"){o.push([n.generateLocator(g,"title",C,{exact:$})]);continue}}if(h.name==="internal:control"&&h.body==="enter-frame"){const E=o[o.length-1],x=l[f-1],_=E.map(N=>n.chainLocators([N,n.generateLocator(g,"frame","")]));["xpath","css"].includes(x.name)&&_.push(n.generateLocator(g,"frame-locator",An({parts:[x]})),n.generateLocator(g,"frame-locator",An({parts:[x]},!0))),E.splice(0,E.length,..._),u="frame-locator";continue}const y=l[f+1],m=An({parts:[h]}),w=n.generateLocator(g,"default",m);if(y&&["internal:has-text","internal:has-not-text"].includes(y.name)){const{exact:E,text:x}=La(y.body);if(!E){const _=n.generateLocator("locator",y.name==="internal:has-text"?"has-text":"has-not-text",x,{exact:E}),N={};y.name==="internal:has-text"?N.hasText=x:N.hasNotText=x;const C=n.generateLocator(g,"default",m,N);o.push([n.chainLocators([w,_]),C]),f++;continue}}let v;if(["xpath","css"].includes(h.name)){const E=An({parts:[h]},!0);v=n.generateLocator(g,"default",E)}o.push([w,v].filter(Boolean))}return u_(n,o,r)}function u_(n,e,i){const r=e.map(()=>""),l=[],o=u=>{if(u===e.length)return l.push(n.chainLocators(r)),l.lengthJSON.parse(r));for(let r=0;ry_(e,f,m.expandedItems,x||0,u),[e,f,m,x,u]),N=U.useRef(null),[C,$]=U.useState(),[I,D]=U.useState(!1);U.useEffect(()=>{y==null||y(C)},[y,C]),U.useEffect(()=>{const q=N.current;if(!q)return;const j=()=>{s0.set(n,q.scrollTop)};return q.addEventListener("scroll",j,{passive:!0}),()=>q.removeEventListener("scroll",j)},[n]),U.useEffect(()=>{N.current&&(N.current.scrollTop=s0.get(n)||0)},[n]);const K=U.useCallback(q=>{const{expanded:j}=_.get(q);if(j){for(let ne=f;ne;ne=ne.parent)if(ne===q){g==null||g(q);break}m.expandedItems.set(q.id,!1)}else m.expandedItems.set(q.id,!0);w({...m})},[_,f,g,m,w]),Q=U.useCallback(q=>{const{expanded:j}=_.get(q),ne=[q];for(;ne.length;){const le=ne.pop();ne.push(...le.children),m.expandedItems.set(le.id,!j)}w({...m})},[_,m,w]);return S.jsx("div",{className:Fe("tree-view vbox",n+"-tree-view"),"data-testid":E||n+"-tree",children:S.jsxs("div",{className:Fe("tree-view-content"),role:_.size>0?"tree":void 0,tabIndex:0,onKeyDown:q=>{if(f&&q.key==="Enter"){h==null||h(f);return}if(q.key!=="ArrowDown"&&q.key!=="ArrowUp"&&q.key!=="ArrowLeft"&&q.key!=="ArrowRight")return;if(q.stopPropagation(),q.preventDefault(),f&&q.key==="ArrowLeft"){const{expanded:ne,parent:le}=_.get(f);ne?(m.expandedItems.set(f.id,!1),w({...m})):le&&(g==null||g(le));return}if(f&&q.key==="ArrowRight"){f.children.length&&(m.expandedItems.set(f.id,!0),w({...m}));return}let j=f;if(q.key==="ArrowDown"&&(f?j=_.get(f).next:_.size&&(j=[..._.keys()][0])),q.key==="ArrowUp"){if(f)j=_.get(f).prev;else if(_.size){const ne=[..._.keys()];j=ne[ne.length-1]}}y==null||y(void 0),j&&(D(!0),g==null||g(j)),$(void 0)},ref:N,children:[v&&_.size===0&&S.jsx("div",{className:"tree-view-empty",children:v}),e.children.map(q=>_.get(q)&&S.jsx(kb,{item:q,treeItems:_,selectedItem:f,onSelected:g,onAccepted:h,isError:o,toggleExpanded:K,toggleSubtree:Q,highlightedItem:C,setHighlightedItem:$,render:i,icon:l,title:r,isKeyboardNavigation:I,setIsKeyboardNavigation:D},q.id))]})})}function kb({item:n,treeItems:e,selectedItem:i,onSelected:r,highlightedItem:l,setHighlightedItem:o,isError:u,onAccepted:f,toggleExpanded:h,toggleSubtree:g,render:y,title:m,icon:w,isKeyboardNavigation:v,setIsKeyboardNavigation:E}){const x=U.useId(),_=U.useRef(null);U.useEffect(()=>{i===n&&v&&_.current&&(J0(_.current),E(!1))},[n,i,v,E]);const N=e.get(n),C=N.depth,$=N.expanded;let I="codicon-blank";typeof $=="boolean"&&(I=$?"codicon-chevron-down":"codicon-chevron-right");const D=y(n),K=$&&n.children.length?n.children:[],Q=m==null?void 0:m(n),q=(w==null?void 0:w(n))||"codicon-blank";return S.jsxs("div",{ref:_,role:"treeitem","aria-selected":n===i,"aria-expanded":$,"aria-controls":x,title:Q,className:"vbox",style:{flex:"none"},children:[S.jsxs("div",{onDoubleClick:()=>f==null?void 0:f(n),className:Fe("tree-view-entry",i===n&&"selected",l===n&&"highlighted",(u==null?void 0:u(n))&&"error"),onClick:()=>r==null?void 0:r(n),onMouseEnter:()=>o(n),onMouseLeave:()=>o(void 0),children:[C?new Array(C).fill(0).map((j,ne)=>S.jsx("div",{className:"tree-view-indent"},"indent-"+ne)):void 0,S.jsx("div",{"aria-hidden":"true",className:"codicon "+I,style:{minWidth:16,marginRight:4},onDoubleClick:j=>{j.preventDefault(),j.stopPropagation()},onClick:j=>{j.stopPropagation(),j.preventDefault(),j.altKey?g(n):h(n)}}),w&&S.jsx("div",{className:"codicon "+q,style:{minWidth:16,marginRight:4},"aria-label":"["+q.replace("codicon","icon")+"]"}),typeof D=="string"?S.jsx("div",{style:{textOverflow:"ellipsis",overflow:"hidden"},children:D}):D]}),!!K.length&&S.jsx("div",{id:x,role:"group",children:K.map(j=>e.get(j)&&S.jsx(kb,{item:j,treeItems:e,selectedItem:i,onSelected:r,onAccepted:f,isError:u,toggleExpanded:h,toggleSubtree:g,highlightedItem:l,setHighlightedItem:o,render:y,title:m,icon:w,isKeyboardNavigation:v,setIsKeyboardNavigation:E},j.id))})]})}function y_(n,e,i,r,l=()=>!0){if(!l(n))return new Map;const o=new Map,u=new Set;for(let g=e==null?void 0:e.parent;g;g=g.parent)u.add(g.id);let f=null;const h=(g,y)=>{for(const m of g.children){if(!l(m))continue;const w=u.has(m.id)||i.get(m.id),v=r>y&&o.size<25&&w!==!1,E=m.children.length?w??v:void 0,x={depth:y,expanded:E,parent:n===g?null:g,next:null,prev:f};f&&(o.get(f).next=m),f=m,o.set(m,x),E&&h(m,y+1)}};return h(n,0),o}const Ht=U.forwardRef(function({children:e,title:i="",icon:r,disabled:l=!1,toggled:o=!1,onClick:u=()=>{},style:f,testId:h,className:g,ariaLabel:y},m){return S.jsxs("button",{ref:m,className:Fe(g,"toolbar-button",r,o&&"toggled"),onMouseDown:r0,onClick:u,onDoubleClick:r0,title:i,disabled:!!l,style:f,"data-testid":h,"aria-label":y||i,children:[r&&S.jsx("span",{className:`codicon codicon-${r}`,style:e?{marginRight:5}:{}}),e]})}),r0=n=>{n.stopPropagation(),n.preventDefault()};function Mb(n){return n==="scheduled"?"codicon-clock":n==="running"?"codicon-loading":n==="failed"?"codicon-error":n==="passed"?"codicon-check":n==="skipped"?"codicon-circle-slash":"codicon-circle-outline"}function b_(n){return n==="scheduled"?"Pending":n==="running"?"Running":n==="failed"?"Failed":n==="passed"?"Passed":n==="skipped"?"Skipped":"Did not run"}const v_=m_,S_=({actions:n,selectedAction:e,selectedTime:i,setSelectedTime:r,treeState:l,setTreeState:o,sdkLanguage:u,onSelected:f,onHighlighted:h,revealConsole:g,revealActionAttachment:y,isLive:m})=>{const{rootItem:w,itemMap:v}=U.useMemo(()=>sb(n),[n]),{selectedItem:E}=U.useMemo(()=>({selectedItem:e?v.get(e.callId):void 0}),[v,e]),x=U.useCallback(D=>{var K;return!!((K=D.action.error)!=null&&K.message)},[]),_=U.useCallback(D=>r({minimum:D.action.startTime,maximum:D.action.endTime}),[r]),N=U.useCallback(D=>{var Q;const K=!!y&&!!((Q=D.action.attachments)!=null&&Q.length);return ed(D.action,{sdkLanguage:u,revealConsole:g,revealActionAttachment:()=>y==null?void 0:y(D.action.callId),isLive:m,showDuration:!0,showBadges:!0,showAttachments:K})},[m,g,y,u]),C=U.useCallback(D=>!i||!D.action||D.action.startTime<=i.maximum&&D.action.endTime>=i.minimum,[i]),$=U.useCallback(D=>{f==null||f(D.action)},[f]),I=U.useCallback(D=>{h==null||h(D==null?void 0:D.action)},[h]);return S.jsxs("div",{className:"vbox",children:[i&&S.jsxs("div",{className:"action-list-show-all",onClick:()=>r(void 0),children:[S.jsx("span",{className:"codicon codicon-triangle-left"}),"Show all"]}),S.jsx(v_,{name:"actions",rootItem:w,treeState:l,setTreeState:o,selectedItem:E,onSelected:$,onHighlighted:I,onAccepted:_,isError:x,isVisible:C,render:N})]})},ed=(n,e)=>{var _;const{sdkLanguage:i,revealConsole:r,revealActionAttachment:l,isLive:o,showDuration:u,showBadges:f,showAttachments:h}=e,{errors:g,warnings:y}=Dx(n),m=n.params.selector?o_(i||"javascript",n.params.selector):void 0,w=n.class==="Test"&&n.method==="test.step"&&((_=n.annotations)==null?void 0:_.some(N=>N.type==="skip"));let v="";n.endTime?v=Et(n.endTime-n.startTime):n.error?v="Timed out":o||(v="-");const{elements:E,title:x}=Ob(n);return S.jsxs("div",{className:"action-title vbox",children:[S.jsxs("div",{className:"hbox",children:[S.jsx("span",{className:"action-title-method",title:x,children:E}),(u||f||h||w)&&S.jsx("div",{className:"spacer"}),h&&S.jsx(Ht,{icon:"attach",title:"Open Attachment",onClick:()=>l==null?void 0:l()}),u&&!w&&S.jsx("div",{className:"action-duration",children:v||S.jsx("span",{className:"codicon codicon-loading"})}),w&&S.jsx("span",{className:Fe("action-skipped","codicon",Mb("skipped")),title:"skipped"}),f&&S.jsxs("div",{className:"action-icons",onClick:()=>r==null?void 0:r(),children:[!!g&&S.jsxs("div",{className:"action-icon",children:[S.jsx("span",{className:"codicon codicon-error"}),S.jsx("span",{className:"action-icon-value",children:g})]}),!!y&&S.jsxs("div",{className:"action-icon",children:[S.jsx("span",{className:"codicon codicon-warning"}),S.jsx("span",{className:"action-icon-value",children:y})]})]})]}),m&&S.jsx("div",{className:"action-title-selector",title:m,children:m})]})};function Ob(n){var f;let e=n.title??((f=Qh.get(n.class+"."+n.method))==null?void 0:f.title)??n.method;e=e.replace(/\n/g," ");const i=[],r=[];let l=0;const o=/\{([^}]+)\}/g;let u;for(;(u=o.exec(e))!==null;){const[h,g]=u,y=e.slice(l,u.index);i.push(y),r.push(y);const m=eb(n.params,g);m===void 0?(i.push(h),r.push(h)):u.index===0?(i.push(m),r.push(m)):(i.push(S.jsx("span",{className:"action-title-param",children:m},i.length)),r.push(m)),l=u.index+h.length}if(l{const[i,r]=U.useState("copy"),l=U.useCallback(()=>{(typeof n=="function"?n():Promise.resolve(n)).then(u=>{navigator.clipboard.writeText(u).then(()=>{r("check"),setTimeout(()=>{r("copy")},3e3)},()=>{r("close")})},()=>{r("close")})},[n]);return S.jsx(Ht,{title:e||"Copy",icon:i,onClick:l})},Ko=({value:n,description:e,copiedDescription:i=e,style:r})=>{const[l,o]=U.useState(!1),u=U.useCallback(async()=>{const f=typeof n=="function"?await n():n;await navigator.clipboard.writeText(f),o(!0),setTimeout(()=>o(!1),3e3)},[n]);return S.jsx(Ht,{style:r,title:e,onClick:u,className:"copy-to-clipboard-text-button",children:l?i:e})},ms=({text:n})=>S.jsx("div",{className:"fill",style:{display:"flex",alignItems:"center",justifyContent:"center",fontSize:24,fontWeight:"bold",opacity:.5},children:n}),w_=({action:n,startTimeOffset:e,sdkLanguage:i})=>{const r=U.useMemo(()=>Object.keys((n==null?void 0:n.params)??{}).filter(f=>f!=="info"),[n]);if(!n)return S.jsx(ms,{text:"No action selected"});const l=n.startTime-e,o=Et(l),{title:u}=Ob(n);return S.jsxs("div",{className:"call-tab",children:[S.jsx("div",{className:"call-line",children:u}),S.jsx("div",{className:"call-section",children:"Time"}),Oo({name:"start",type:"literal",text:o}),Oo({name:"duration",type:"literal",text:x_(n)}),!!r.length&&S.jsxs(S.Fragment,{children:[S.jsx("div",{className:"call-section",children:"Parameters"}),r.map(f=>Oo(a0(n,f,n.params[f],i)))]}),!!n.result&&S.jsxs(S.Fragment,{children:[S.jsx("div",{className:"call-section",children:"Return value"}),Object.keys(n.result).map(f=>Oo(a0(n,f,n.result[f],i)))]})]})};function x_(n){return n.endTime?Et(n.endTime-n.startTime):n.error?"Timed Out":"Running"}function Oo(n){let e=n.text.replace(/\n/g,"↵");return n.type==="string"&&(e=`"${e}"`),S.jsxs("div",{className:"call-line",children:[n.name,":",S.jsx("span",{className:Fe("call-value",n.type),title:n.text,children:e}),["literal","string","number","object","locator"].includes(n.type)&&S.jsx(td,{value:n.text})]},n.name)}function a0(n,e,i,r){const l=n.method.includes("eval")||n.method==="waitForFunction";if(e==="files")return{text:"",type:"string",name:e};if((e==="eventInit"||e==="expectedValue"||e==="arg"&&l)&&(i=ac(i.value,new Array(10).fill({handle:""}))),(e==="value"&&l||e==="received"&&n.method==="expect")&&(i=ac(i,new Array(10).fill({handle:""}))),e==="selector")return{text:Oi(r||"javascript",n.params.selector),type:"locator",name:"locator"};const o=typeof i;return o!=="object"||i===null?{text:String(i),type:o,name:e}:i.guid?{text:"",type:"handle",name:e}:{text:JSON.stringify(i).slice(0,1e3),type:"object",name:e}}function ac(n,e){if(n.n!==void 0)return n.n;if(n.s!==void 0)return n.s;if(n.b!==void 0)return n.b;if(n.v!==void 0){if(n.v==="undefined")return;if(n.v==="null")return null;if(n.v==="NaN")return NaN;if(n.v==="Infinity")return 1/0;if(n.v==="-Infinity")return-1/0;if(n.v==="-0")return-0}if(n.d!==void 0)return new Date(n.d);if(n.r!==void 0)return new RegExp(n.r.p,n.r.f);if(n.a!==void 0)return n.a.map(i=>ac(i,e));if(n.o!==void 0){const i={};for(const{k:r,v:l}of n.o)i[r]=ac(l,e);return i}return n.h!==void 0?e===void 0?"":e[n.h]:""}const l0=new Map;function yc({name:n,items:e=[],id:i,render:r,icon:l,isError:o,isWarning:u,isInfo:f,selectedItem:h,onAccepted:g,onSelected:y,onHighlighted:m,onIconClicked:w,noItemsMessage:v,dataTestId:E,notSelectable:x,ariaLabel:_}){const N=U.useRef(null),[C,$]=U.useState();return U.useEffect(()=>{m==null||m(C)},[m,C]),U.useEffect(()=>{const I=N.current;if(!I)return;const D=()=>{l0.set(n,I.scrollTop)};return I.addEventListener("scroll",D,{passive:!0}),()=>I.removeEventListener("scroll",D)},[n]),U.useEffect(()=>{N.current&&(N.current.scrollTop=l0.get(n)||0)},[n]),S.jsx("div",{className:Fe("list-view vbox",n+"-list-view"),role:e.length>0?"list":void 0,"aria-label":_,children:S.jsxs("div",{className:Fe("list-view-content",x&&"not-selectable"),tabIndex:0,onKeyDown:I=>{var q;if(h&&I.key==="Enter"){g==null||g(h,e.indexOf(h));return}if(I.key!=="ArrowDown"&&I.key!=="ArrowUp")return;I.stopPropagation(),I.preventDefault();const D=h?e.indexOf(h):-1;let K=D;I.key==="ArrowDown"&&(D===-1?K=0:K=Math.min(D+1,e.length-1)),I.key==="ArrowUp"&&(D===-1?K=e.length-1:K=Math.max(D-1,0));const Q=(q=N.current)==null?void 0:q.children.item(K);J0(Q||void 0),m==null||m(void 0),y==null||y(e[K],K),$(void 0)},ref:N,children:[v&&e.length===0&&S.jsx("div",{className:"list-view-empty",children:v}),e.map((I,D)=>{const K=r(I,D);return S.jsxs("div",{onDoubleClick:()=>g==null?void 0:g(I,D),role:"listitem",className:Fe("list-view-entry",h===I&&"selected",!x&&C===I&&"highlighted",(o==null?void 0:o(I,D))&&"error",(u==null?void 0:u(I,D))&&"warning",(f==null?void 0:f(I,D))&&"info"),"aria-selected":h===I,onClick:()=>y==null?void 0:y(I,D),onMouseEnter:()=>$(I),onMouseLeave:()=>$(void 0),children:[l&&S.jsx("div",{className:"codicon "+(l(I,D)||"codicon-blank"),style:{minWidth:16,marginRight:4},onDoubleClick:Q=>{Q.preventDefault(),Q.stopPropagation()},onClick:Q=>{Q.stopPropagation(),Q.preventDefault(),w==null||w(I,D)}}),typeof K=="string"?S.jsx("div",{style:{textOverflow:"ellipsis",overflow:"hidden"},children:K}):K]},(i==null?void 0:i(I,D))||D)})]})})}const __=yc,T_=({action:n,isLive:e})=>{const i=U.useMemo(()=>{var u;if(!n||!n.log.length)return[];const r=n.log,l=n.context.wallTime-n.context.startTime,o=[];for(let f=0;f0?h=Et(n.endTime-g):e?h=Et(Date.now()-l-g):h="-"}o.push({message:r[f].message,time:h})}return o},[n,e]);return i.length?S.jsx(__,{name:"log",ariaLabel:"Log entries",items:i,render:r=>S.jsxs("div",{className:"log-list-item",children:[S.jsx("span",{className:"log-list-duration",children:r.time}),r.message]}),notSelectable:!0}):S.jsx(ms,{text:"No log entries"})};function nl(n,e){const i=/(\x1b\[(\d+(;\d+)*)m)|([^\x1b]+)/g,r=[];let l,o={},u=!1,f=e==null?void 0:e.fg,h=e==null?void 0:e.bg;for(;(l=i.exec(n))!==null;){const[,,g,,y]=l;if(g){const m=+g;switch(m){case 0:o={};break;case 1:o["font-weight"]="bold";break;case 2:o.opacity="0.8";break;case 3:o["font-style"]="italic";break;case 4:o["text-decoration"]="underline";break;case 7:u=!0;break;case 8:o.display="none";break;case 9:o["text-decoration"]="line-through";break;case 22:delete o["font-weight"],delete o["font-style"],delete o.opacity,delete o["text-decoration"];break;case 23:delete o["font-weight"],delete o["font-style"],delete o.opacity;break;case 24:delete o["text-decoration"];break;case 27:u=!1;break;case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:f=o0[m-30];break;case 39:f=e==null?void 0:e.fg;break;case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:h=o0[m-40];break;case 49:h=e==null?void 0:e.bg;break;case 53:o["text-decoration"]="overline";break;case 90:case 91:case 92:case 93:case 94:case 95:case 96:case 97:f=c0[m-90];break;case 100:case 101:case 102:case 103:case 104:case 105:case 106:case 107:h=c0[m-100];break}}else if(y){const m={...o},w=u?h:f;w!==void 0&&(m.color=w);const v=u?f:h;v!==void 0&&(m["background-color"]=v),r.push(`${E_(y)}`)}}return r.join("")}const o0={0:"var(--vscode-terminal-ansiBlack)",1:"var(--vscode-terminal-ansiRed)",2:"var(--vscode-terminal-ansiGreen)",3:"var(--vscode-terminal-ansiYellow)",4:"var(--vscode-terminal-ansiBlue)",5:"var(--vscode-terminal-ansiMagenta)",6:"var(--vscode-terminal-ansiCyan)",7:"var(--vscode-terminal-ansiWhite)"},c0={0:"var(--vscode-terminal-ansiBrightBlack)",1:"var(--vscode-terminal-ansiBrightRed)",2:"var(--vscode-terminal-ansiBrightGreen)",3:"var(--vscode-terminal-ansiBrightYellow)",4:"var(--vscode-terminal-ansiBrightBlue)",5:"var(--vscode-terminal-ansiBrightMagenta)",6:"var(--vscode-terminal-ansiBrightCyan)",7:"var(--vscode-terminal-ansiBrightWhite)"};function E_(n){return n.replace(/[&"<>]/g,e=>({"&":"&",'"':""","<":"<",">":">"})[e])}function A_(n){return Object.entries(n).map(([e,i])=>`${e}: ${i}`).join("; ")}const N_=({error:n})=>{const e=U.useMemo(()=>nl(n),[n]);return S.jsx("div",{className:"error-message",dangerouslySetInnerHTML:{__html:e||""}})},jb=({cursor:n,onPaneMouseMove:e,onPaneMouseUp:i,onPaneDoubleClick:r})=>(gt.useEffect(()=>{const l=document.createElement("div");return l.style.position="fixed",l.style.top="0",l.style.right="0",l.style.bottom="0",l.style.left="0",l.style.zIndex="9999",l.style.cursor=n,document.body.appendChild(l),e&&l.addEventListener("mousemove",e),i&&l.addEventListener("mouseup",i),r&&document.body.addEventListener("dblclick",r),()=>{e&&l.removeEventListener("mousemove",e),i&&l.removeEventListener("mouseup",i),r&&document.body.removeEventListener("dblclick",r),document.body.removeChild(l)}},[n,e,i,r]),S.jsx(S.Fragment,{})),C_={position:"absolute",top:0,right:0,bottom:0,left:0},Lb=({orientation:n,offsets:e,setOffsets:i,resizerColor:r,resizerWidth:l,minColumnWidth:o})=>{const u=o||0,[f,h]=gt.useState(null),[g,y]=gs(),m={position:"absolute",right:n==="horizontal"?void 0:0,bottom:n==="horizontal"?0:void 0,width:n==="horizontal"?7:void 0,height:n==="horizontal"?void 0:7,borderTopWidth:n==="horizontal"?void 0:(7-l)/2,borderRightWidth:n==="horizontal"?(7-l)/2:void 0,borderBottomWidth:n==="horizontal"?void 0:(7-l)/2,borderLeftWidth:n==="horizontal"?(7-l)/2:void 0,borderColor:"transparent",borderStyle:"solid",cursor:n==="horizontal"?"ew-resize":"ns-resize"};return S.jsxs("div",{style:{position:"absolute",top:0,right:0,bottom:0,left:-(7-l)/2,zIndex:100,pointerEvents:"none"},ref:y,children:[!!f&&S.jsx(jb,{cursor:n==="horizontal"?"ew-resize":"ns-resize",onPaneMouseUp:()=>h(null),onPaneMouseMove:w=>{if(!w.buttons)h(null);else if(f){const v=n==="horizontal"?w.clientX-f.clientX:w.clientY-f.clientY,E=f.offset+v,x=f.index>0?e[f.index-1]:0,_=n==="horizontal"?g.width:g.height,N=Math.min(Math.max(x+u,E),_-u)-e[f.index];for(let C=f.index;CS.jsx("div",{style:{...m,top:n==="horizontal"?0:w,left:n==="horizontal"?w:0,pointerEvents:"initial"},onMouseDown:E=>h({clientX:E.clientX,clientY:E.clientY,offset:w,index:v}),children:S.jsx("div",{style:{...C_,background:r}})},v))]})};async function lh(n){const e=new Image;return n&&(e.src=n,await new Promise((i,r)=>{e.onload=i,e.onerror=i})),e}const Oh={backgroundImage:`linear-gradient(45deg, #80808020 25%, transparent 25%), - linear-gradient(-45deg, #80808020 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, #80808020 75%), - linear-gradient(-45deg, transparent 75%, #80808020 75%)`,backgroundSize:"20px 20px",backgroundPosition:"0 0, 0 10px, 10px -10px, -10px 0px",boxShadow:`rgb(0 0 0 / 10%) 0px 1.8px 1.9px, - rgb(0 0 0 / 15%) 0px 6.1px 6.3px, - rgb(0 0 0 / 10%) 0px -2px 4px, - rgb(0 0 0 / 15%) 0px -6.1px 12px, - rgb(0 0 0 / 25%) 0px 6px 12px`},k_=({diff:n,noTargetBlank:e,hideDetails:i})=>{const[r,l]=U.useState(n.diff?"diff":"actual"),[o,u]=U.useState(!1),[f,h]=U.useState(null),[g,y]=U.useState("Expected"),[m,w]=U.useState(null),[v,E]=U.useState(null),[x,_]=gs();U.useEffect(()=>{(async()=>{var j,ne,le,V;h(await lh((j=n.expected)==null?void 0:j.attachment.path)),y(((ne=n.expected)==null?void 0:ne.title)||"Expected"),w(await lh((le=n.actual)==null?void 0:le.attachment.path)),E(await lh((V=n.diff)==null?void 0:V.attachment.path))})()},[n]);const N=f&&m&&v,C=N?Math.max(f.naturalWidth,m.naturalWidth,200):500,$=N?Math.max(f.naturalHeight,m.naturalHeight,200):500,I=Math.min(1,(x.width-30)/C),D=Math.min(1,(x.width-50)/C/2),K=C*I,Q=$*I,q={flex:"none",margin:"0 10px",cursor:"pointer",userSelect:"none"};return S.jsx("div",{"data-testid":"test-result-image-mismatch",style:{display:"flex",flexDirection:"column",alignItems:"center",flex:"auto"},ref:_,children:N&&S.jsxs(S.Fragment,{children:[S.jsxs("div",{"data-testid":"test-result-image-mismatch-tabs",style:{display:"flex",margin:"10px 0 20px"},children:[n.diff&&S.jsx("div",{style:{...q,fontWeight:r==="diff"?600:"initial"},onClick:()=>l("diff"),children:"Diff"}),S.jsx("div",{style:{...q,fontWeight:r==="actual"?600:"initial"},onClick:()=>l("actual"),children:"Actual"}),S.jsx("div",{style:{...q,fontWeight:r==="expected"?600:"initial"},onClick:()=>l("expected"),children:g}),S.jsx("div",{style:{...q,fontWeight:r==="sxs"?600:"initial"},onClick:()=>l("sxs"),children:"Side by side"}),S.jsx("div",{style:{...q,fontWeight:r==="slider"?600:"initial"},onClick:()=>l("slider"),children:"Slider"})]}),S.jsxs("div",{style:{display:"flex",justifyContent:"center",flex:"auto",minHeight:Q+60},children:[n.diff&&r==="diff"&&S.jsx(Jn,{image:v,alt:"Diff",hideSize:i,canvasWidth:K,canvasHeight:Q,scale:I}),n.diff&&r==="actual"&&S.jsx(Jn,{image:m,alt:"Actual",hideSize:i,canvasWidth:K,canvasHeight:Q,scale:I}),n.diff&&r==="expected"&&S.jsx(Jn,{image:f,alt:g,hideSize:i,canvasWidth:K,canvasHeight:Q,scale:I}),n.diff&&r==="slider"&&S.jsx(M_,{expectedImage:f,actualImage:m,hideSize:i,canvasWidth:K,canvasHeight:Q,scale:I,expectedTitle:g}),n.diff&&r==="sxs"&&S.jsxs("div",{style:{display:"flex"},children:[S.jsx(Jn,{image:f,title:g,hideSize:i,canvasWidth:D*C,canvasHeight:D*$,scale:D}),S.jsx(Jn,{image:o?v:m,title:o?"Diff":"Actual",onClick:()=>u(!o),hideSize:i,canvasWidth:D*C,canvasHeight:D*$,scale:D})]}),!n.diff&&r==="actual"&&S.jsx(Jn,{image:m,title:"Actual",hideSize:i,canvasWidth:K,canvasHeight:Q,scale:I}),!n.diff&&r==="expected"&&S.jsx(Jn,{image:f,title:g,hideSize:i,canvasWidth:K,canvasHeight:Q,scale:I}),!n.diff&&r==="sxs"&&S.jsxs("div",{style:{display:"flex"},children:[S.jsx(Jn,{image:f,title:g,canvasWidth:D*C,canvasHeight:D*$,scale:D}),S.jsx(Jn,{image:m,title:"Actual",canvasWidth:D*C,canvasHeight:D*$,scale:D})]})]}),!i&&S.jsxs("div",{style:{alignSelf:"start",lineHeight:"18px",marginLeft:"15px"},children:[S.jsx("div",{children:n.diff&&S.jsx("a",{target:"_blank",href:n.diff.attachment.path,rel:"noreferrer",children:n.diff.attachment.name})}),S.jsx("div",{children:S.jsx("a",{target:e?"":"_blank",href:n.actual.attachment.path,rel:"noreferrer",children:n.actual.attachment.name})}),S.jsx("div",{children:S.jsx("a",{target:e?"":"_blank",href:n.expected.attachment.path,rel:"noreferrer",children:n.expected.attachment.name})})]})]})})},M_=({expectedImage:n,actualImage:e,canvasWidth:i,canvasHeight:r,scale:l,expectedTitle:o,hideSize:u})=>{const f={position:"absolute",top:0,left:0},[h,g]=U.useState(i/2),y=n.naturalWidth===e.naturalWidth&&n.naturalHeight===e.naturalHeight;return S.jsxs("div",{style:{flex:"none",display:"flex",alignItems:"center",flexDirection:"column",userSelect:"none"},children:[!u&&S.jsxs("div",{style:{margin:5},children:[!y&&S.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"Expected "}),S.jsx("span",{children:n.naturalWidth}),S.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"x"}),S.jsx("span",{children:n.naturalHeight}),!y&&S.jsx("span",{style:{flex:"none",margin:"0 5px 0 15px"},children:"Actual "}),!y&&S.jsx("span",{children:e.naturalWidth}),!y&&S.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"x"}),!y&&S.jsx("span",{children:e.naturalHeight})]}),S.jsxs("div",{style:{position:"relative",width:i,height:r,margin:15,...Oh},children:[S.jsx(Lb,{orientation:"horizontal",offsets:[h],setOffsets:m=>g(m[0]),resizerColor:"#57606a80",resizerWidth:6}),S.jsx("img",{alt:o,style:{width:n.naturalWidth*l,height:n.naturalHeight*l},draggable:"false",src:n.src}),S.jsx("div",{style:{...f,bottom:0,overflow:"hidden",width:h,...Oh},children:S.jsx("img",{alt:"Actual",style:{width:e.naturalWidth*l,height:e.naturalHeight*l},draggable:"false",src:e.src})})]})]})},Jn=({image:n,title:e,alt:i,hideSize:r,canvasWidth:l,canvasHeight:o,scale:u,onClick:f})=>S.jsxs("div",{style:{flex:"none",display:"flex",alignItems:"center",flexDirection:"column"},children:[!r&&S.jsxs("div",{style:{margin:5},children:[e&&S.jsx("span",{style:{flex:"none",margin:"0 5px"},children:e}),S.jsx("span",{children:n.naturalWidth}),S.jsx("span",{style:{flex:"none",margin:"0 5px"},children:"x"}),S.jsx("span",{children:n.naturalHeight})]}),S.jsx("div",{style:{display:"flex",flex:"none",width:l,height:o,margin:15,...Oh},children:S.jsx("img",{width:n.naturalWidth*u,height:n.naturalHeight*u,alt:e||i,style:{cursor:f?"pointer":"initial"},draggable:"false",src:n.src,onClick:f})})]}),O_="modulepreload",j_=function(n,e){return new URL(n,e).href},u0={},L_=function(e,i,r){let l=Promise.resolve();if(i&&i.length>0){let u=function(y){return Promise.all(y.map(m=>Promise.resolve(m).then(w=>({status:"fulfilled",value:w}),w=>({status:"rejected",reason:w}))))};const f=document.getElementsByTagName("link"),h=document.querySelector("meta[property=csp-nonce]"),g=(h==null?void 0:h.nonce)||(h==null?void 0:h.getAttribute("nonce"));l=u(i.map(y=>{if(y=j_(y,r),y in u0)return;u0[y]=!0;const m=y.endsWith(".css"),w=m?'[rel="stylesheet"]':"";if(!!r)for(let x=f.length-1;x>=0;x--){const _=f[x];if(_.href===y&&(!m||_.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${y}"]${w}`))return;const E=document.createElement("link");if(E.rel=m?"stylesheet":O_,m||(E.as="script"),E.crossOrigin="",E.href=y,g&&E.setAttribute("nonce",g),document.head.appendChild(E),m)return new Promise((x,_)=>{E.addEventListener("load",x),E.addEventListener("error",()=>_(new Error(`Unable to preload CSS for ${y}`)))})}))}function o(u){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=u,window.dispatchEvent(f),!f.defaultPrevented)throw u}return l.then(u=>{for(const f of u||[])f.status==="rejected"&&o(f.reason);return e().catch(o)})},R_=20,xr=({text:n,highlighter:e,mimeType:i,linkify:r,readOnly:l,highlight:o,revealLine:u,lineNumbers:f,isFocused:h,focusOnChange:g,wrapLines:y,onChange:m,dataTestId:w,placeholder:v})=>{const[E,x]=gs(),[_]=U.useState(L_(()=>import("./codeMirrorModule-a5XoALAZ.js"),__vite__mapDeps([0,1]),import.meta.url).then(I=>I.default)),N=U.useRef(null),[C,$]=U.useState();return U.useEffect(()=>{(async()=>{var q,j;const I=await _;z_(I);const D=x.current;if(!D)return;const K=U_(e)||B_(i)||(r?"text/linkified":"");if(N.current&&K===N.current.cm.getOption("mode")&&!!l===N.current.cm.getOption("readOnly")&&f===N.current.cm.getOption("lineNumbers")&&y===N.current.cm.getOption("lineWrapping")&&v===N.current.cm.getOption("placeholder"))return;(j=(q=N.current)==null?void 0:q.cm)==null||j.getWrapperElement().remove();const Q=I(D,{value:"",mode:K,readOnly:!!l,lineNumbers:f,lineWrapping:y,placeholder:v,matchBrackets:!0,autoCloseBrackets:!0,extraKeys:{"Ctrl-F":"findPersistent","Cmd-F":"findPersistent"}});return N.current={cm:Q},h&&Q.focus(),$(Q),Q})()},[_,C,x,e,i,r,f,y,l,h,v]),U.useEffect(()=>{N.current&&N.current.cm.setSize(E.width,E.height)},[E]),U.useLayoutEffect(()=>{var K;if(!C)return;let I=!1;if(C.getValue()!==n&&(C.setValue(n),I=!0,g&&(C.execCommand("selectAll"),C.focus())),I||JSON.stringify(o)!==JSON.stringify(N.current.highlight)){for(const j of N.current.highlight||[])C.removeLineClass(j.line-1,"wrap");for(const j of o||[])C.addLineClass(j.line-1,"wrap",`source-line-${j.type}`);for(const j of N.current.widgets||[])C.removeLineWidget(j);for(const j of N.current.markers||[])j.clear();const Q=[],q=[];for(const j of o||[]){if(j.type!=="subtle-error"&&j.type!=="error")continue;const ne=(K=N.current)==null?void 0:K.cm.getLine(j.line-1);if(ne){const le={};le.title=j.message||"",q.push(C.markText({line:j.line-1,ch:0},{line:j.line-1,ch:j.column||ne.length},{className:"source-line-error-underline",attributes:le}))}if(j.type==="error"){const le=document.createElement("div");le.innerHTML=nl(j.message||""),le.className="source-line-error-widget",Q.push(C.addLineWidget(j.line,le,{above:!0,coverGutter:!1}))}}N.current.highlight=o,N.current.widgets=Q,N.current.markers=q}typeof u=="number"&&N.current.cm.lineCount()>=u&&C.scrollIntoView({line:Math.max(0,u-1),ch:0},50);let D;return m&&(D=()=>m(C.getValue()),C.on("change",D)),()=>{D&&C.off("change",D)}},[C,n,o,u,g,m]),S.jsx("div",{"data-testid":w,className:"cm-wrapper",ref:x,onClick:D_})};function D_(n){var i;if(!(n.target instanceof HTMLElement))return;let e;n.target.classList.contains("cm-linkified")?e=n.target.textContent:n.target.classList.contains("cm-link")&&((i=n.target.nextElementSibling)!=null&&i.classList.contains("cm-url"))&&(e=n.target.nextElementSibling.textContent.slice(1,-1)),e&&(n.preventDefault(),n.stopPropagation(),window.open(e,"_blank"))}let f0=!1;function z_(n){f0||(f0=!0,n.defineSimpleMode("text/linkified",{start:[{regex:P0,token:"linkified"}]}))}function B_(n){if(n){if(n.includes("javascript")||n.includes("json"))return"javascript";if(n.includes("python"))return"python";if(n.includes("csharp"))return"text/x-csharp";if(n.includes("java"))return"text/x-java";if(n.includes("markdown"))return"markdown";if(n.includes("html")||n.includes("svg"))return"htmlmixed";if(n.includes("css"))return"css"}}function U_(n){if(n)return{javascript:"javascript",jsonl:"javascript",python:"python",csharp:"text/x-csharp",java:"text/x-java",markdown:"markdown",html:"htmlmixed",css:"css",yaml:"yaml"}[n]}function H_(n){return!!n.match(/^(application\/json|application\/.*?\+json|text\/(x-)?json)(;\s*charset=.*)?$/)}function q_(n){return!!n.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/)}const Rb=({title:n,children:e,setExpanded:i,expanded:r,expandOnTitleClick:l,className:o})=>{const u=U.useId(),f=U.useId(),h=U.useCallback(()=>i(!r),[r,i]),g=S.jsx("div",{className:Fe("codicon",r?"codicon-chevron-down":"codicon-chevron-right"),style:{cursor:"pointer",color:"var(--vscode-foreground)",marginLeft:"5px"},onClick:l?void 0:h});return S.jsxs("div",{className:Fe("expandable",r&&"expanded",o),children:[l?S.jsxs("div",{id:u,role:"button","aria-expanded":r,"aria-controls":f,className:"expandable-title",onClick:h,children:[g,n]}):S.jsxs("div",{className:"expandable-title",children:[g,n]}),r&&S.jsx("div",{id:f,"aria-labelledby":u,role:"region",className:"expandable-content",children:e})]})};function Db(n){const e=[];let i=0,r;for(;(r=P0.exec(n))!==null;){const o=n.substring(i,r.index);o&&e.push(o);const u=r[0];e.push($_(u)),i=r.index+u.length}const l=n.substring(i);return l&&e.push(l),e}function $_(n){let e=n;return e.startsWith("www.")&&(e="https://"+e),S.jsx("a",{href:e,target:"_blank",rel:"noopener noreferrer",children:n})}const zb=U.createContext(void 0),ti=()=>U.useContext(zb),I_=({attachment:n,reveal:e})=>{const i=ti(),[r,l]=U.useState(!1),[o,u]=U.useState(null),[f,h]=U.useState(null),[g,y]=px(),m=U.useRef(null),w=q_(n.contentType),v=!!n.sha1||!!n.path;U.useEffect(()=>{var _;if(e)return(_=m.current)==null||_.scrollIntoView({behavior:"smooth"}),y()},[e,y]),U.useEffect(()=>{r&&o===null&&f===null&&(h("Loading ..."),fetch(bc(i,n)).then(_=>_.text()).then(_=>{u(_),h(null)}).catch(_=>{h("Failed to load: "+_.message)}))},[i,r,o,f,n]);const E=U.useMemo(()=>{const _=o?o.split(` -`).length:0;return Math.min(Math.max(5,_),20)*R_},[o]),x=S.jsxs("span",{style:{marginLeft:5},ref:m,"aria-label":n.name,children:[S.jsx("span",{children:Db(n.name)}),v&&S.jsx("a",{style:{marginLeft:5},href:Yo(i,n),children:"download"})]});return!w||!v?S.jsx("div",{style:{marginLeft:20},children:x}):S.jsxs("div",{className:Fe(g&&"yellow-flash"),children:[S.jsx(Rb,{title:x,expanded:r,setExpanded:l,expandOnTitleClick:!0,children:f&&S.jsx("i",{children:f})}),r&&o!==null&&S.jsx("div",{className:"vbox",style:{height:E},children:S.jsx(xr,{text:o,readOnly:!0,mimeType:n.contentType,linkify:!0,lineNumbers:!0,wrapLines:!1})})]})},V_=({revealedAttachmentCallId:n})=>{const e=ti(),{diffMap:i,screenshots:r,attachments:l}=U.useMemo(()=>{const o=new Set((e==null?void 0:e.visibleAttachments)??[]),u=new Set,f=new Map;for(const h of o){if(!h.path&&!h.sha1)continue;const g=h.name.match(/^(.*)-(expected|actual|diff)\.png$/);if(g){const y=g[1],m=g[2],w=f.get(y)||{expected:void 0,actual:void 0,diff:void 0};w[m]=h,f.set(y,w),o.delete(h)}else h.contentType.startsWith("image/")&&(u.add(h),o.delete(h))}return{diffMap:f,attachments:o,screenshots:u}},[e]);return!i.size&&!r.size&&!l.size?S.jsx(ms,{text:"No attachments"}):S.jsxs("div",{className:"attachments-tab",children:[[...i.values()].map(({expected:o,actual:u,diff:f})=>S.jsxs(S.Fragment,{children:[o&&u&&S.jsx("div",{className:"attachments-section",children:"Image diff"}),o&&u&&S.jsx(k_,{noTargetBlank:!0,diff:{name:"Image diff",expected:{attachment:{...o,path:Yo(e,o)},title:"Expected"},actual:{attachment:{...u,path:Yo(e,u)}},diff:f?{attachment:{...f,path:Yo(e,f)}}:void 0}})]})),r.size?S.jsx("div",{className:"attachments-section",children:"Screenshots"}):void 0,[...r.values()].map((o,u)=>{const f=bc(e,o);return S.jsxs("div",{className:"attachment-item",children:[S.jsx("div",{children:S.jsx("img",{draggable:"false",src:f})}),S.jsx("div",{children:S.jsx("a",{target:"_blank",href:f,rel:"noreferrer",children:o.name})})]},`screenshot-${u}`)}),l.size?S.jsx("div",{className:"attachments-section",children:"Attachments"}):void 0,[...l.values()].map((o,u)=>S.jsx("div",{className:"attachment-item",children:S.jsx(I_,{attachment:o,reveal:n&&o.callId===n.callId?n:void 0})},G_(o,u)))]})};function bc(n,e){return n&&e.sha1?n.createRelativeUrl(`sha1/${e.sha1}`):`file?path=${encodeURIComponent(e.path)}`}function Yo(n,e){let i=e.contentType?`&dn=${encodeURIComponent(e.name)}`:"";return e.contentType&&(i+=`&dct=${encodeURIComponent(e.contentType)}`),bc(n,e)+i}function G_(n,e){return e+"-"+(n.sha1?"sha1-"+n.sha1:"path-"+n.path)}const K_=` -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. -`.trimStart();async function Y_({testInfo:n,metadata:e,errorContext:i,errors:r,buildCodeFrame:l,stdout:o,stderr:u}){var m;const f=new Set(r.filter(w=>w.message&&!w.message.includes(` -`)).map(w=>w.message));for(const w of r)for(const v of f.keys())(m=w.message)!=null&&m.includes(v)&&f.delete(v);const h=r.filter(w=>!(!w.message||!w.message.includes(` -`)&&!f.has(w.message)));if(!h.length)return;const g=[K_,"# Test info","",n];o&&g.push("","# Stdout","","```",Xo(o),"```"),u&&g.push("","# Stderr","","```",Xo(u),"```"),g.push("","# Error details");for(const w of h)g.push("","```",Xo(w.message||""),"```");i&&g.push(i);const y=await l(h[h.length-1]);return y&&g.push("","# Test source","","```ts",y,"```"),e!=null&&e.gitDiff&&g.push("","# Local changes","","```diff",e.gitDiff,"```"),g.join(` -`)}const X_=new RegExp("([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))","g");function Xo(n){return n.replace(X_,"")}const F_=yc,Q_=({stack:n,setSelectedFrame:e,selectedFrame:i})=>{const r=n||[];return S.jsx(F_,{name:"stack-trace",ariaLabel:"Stack trace",items:r,selectedItem:r[i],render:l=>{const o=l.file[1]===":"?"\\":"/";return S.jsxs(S.Fragment,{children:[S.jsx("span",{className:"stack-trace-frame-function",children:l.function||"(anonymous)"}),S.jsx("span",{className:"stack-trace-frame-location",children:l.file.split(o).pop()}),S.jsx("span",{className:"stack-trace-frame-line",children:":"+l.line})]})},onSelected:l=>e(r.indexOf(l))})},nd=({noShadow:n,children:e,noMinHeight:i,className:r,sidebarBackground:l,onClick:o})=>S.jsx("div",{className:Fe("toolbar",n&&"no-shadow",i&&"no-min-height",r,l&&"toolbar-sidebar-background"),onClick:o,children:e});function J_(n,e,i,r,l){const o=ti();return ec(async()=>{var v,E,x,_;const u=n==null?void 0:n[e],f=u!=null&&u.file?u:l;if(!f)return{source:{file:"",errors:[],content:void 0},targetLine:0,highlight:[]};const h=f.file;let g=i.get(h);g||(g={errors:((v=l==null?void 0:l.source)==null?void 0:v.errors)||[],content:(E=l==null?void 0:l.source)==null?void 0:E.content},i.set(h,g));const y=(f==null?void 0:f.line)||((x=g.errors[0])==null?void 0:x.line)||0,m=r&&h.startsWith(r)?h.substring(r.length+1):h,w=g.errors.map(N=>({type:"error",line:N.line,message:N.message}));if(w.push({line:y,type:"running"}),((_=l==null?void 0:l.source)==null?void 0:_.content)!==void 0)g.content=l.source.content;else if(g.content===void 0||f===l){const N=await Bb(h);try{let C=o?await fetch(o.createRelativeUrl(`sha1/src@${N}.txt`)):void 0;(!C||C.status===404)&&(C=await fetch(`file?path=${encodeURIComponent(h)}`)),C.status>=400?g.content="":g.content=await C.text()}catch{g.content=``}}return{model:o,source:g,highlight:w,targetLine:y,fileName:m,location:f}},[n,e,r,l],{source:{errors:[],content:"Loading…"},highlight:[]})}const P_=({stack:n,sources:e,rootDir:i,fallbackLocation:r,stackFrameLocation:l,onOpenExternally:o})=>{const[u,f]=U.useState(),[h,g]=U.useState(0);U.useEffect(()=>{u!==n&&(f(n),g(0))},[n,u,f,g]);const{source:y,highlight:m,targetLine:w,fileName:v,location:E}=J_(n,h,e,i,r),x=U.useCallback(()=>{E&&(o?o(E):window.location.href=`vscode://file//${E.file}:${E.line}`)},[o,E]),_=((n==null?void 0:n.length)??0)>1,N=Z_(v),C=N.endsWith(".md")?"markdown":"javascript";return S.jsx(nc,{sidebarSize:200,orientation:l==="bottom"?"vertical":"horizontal",sidebarHidden:!_,main:S.jsxs("div",{className:"vbox","data-testid":"source-code",children:[v&&S.jsxs(nd,{children:[S.jsx("div",{className:"source-tab-file-name",title:v,children:S.jsx("div",{children:N})}),S.jsx(td,{description:"Copy filename",value:N}),E&&S.jsx(Ht,{icon:"link-external",title:"Open in VS Code",onClick:x})]}),S.jsx(xr,{text:y.content||"",highlighter:C,highlight:m,revealLine:w,readOnly:!0,lineNumbers:!0,dataTestId:"source-code-mirror"})]}),sidebar:S.jsx(Q_,{stack:n,selectedFrame:h,setSelectedFrame:g})})};async function Bb(n){const e=new TextEncoder().encode(n),i=await crypto.subtle.digest("SHA-1",e),r=[],l=new DataView(i);for(let o=0;oS.jsx(Ko,{value:n,description:"Copy prompt",copiedDescription:S.jsxs(S.Fragment,{children:["Copied ",S.jsx("span",{className:"codicon codicon-copy",style:{marginLeft:"5px"}})]}),style:{width:"120px",justifyContent:"center"}});function eT(n){return U.useMemo(()=>{if(!n)return{errors:new Map};const e=new Map;for(const i of n.errorDescriptors)e.set(i.message,i);return{errors:e}},[n])}function tT({message:n,error:e,sdkLanguage:i,revealInSource:r}){var f;let l,o;const u=(f=e.stack)==null?void 0:f[0];return u&&(l=u.file.replace(/.*[/\\](.*)/,"$1")+":"+u.line,o=u.file+":"+u.line),S.jsxs("div",{style:{display:"flex",flexDirection:"column",overflowX:"clip"},children:[S.jsxs("div",{className:"hbox",style:{alignItems:"center",padding:"5px 10px",minHeight:36,fontWeight:"bold",color:"var(--vscode-errorForeground)",flex:0},children:[e.action&&ed(e.action,{sdkLanguage:i}),l&&S.jsxs("div",{className:"action-location",children:["@ ",S.jsx("span",{title:o,onClick:()=>r(e),children:l})]})]}),S.jsx(N_,{error:n})]})}const nT=({errorsModel:n,sdkLanguage:e,revealInSource:i,wallTime:r,testRunMetadata:l})=>{const o=ti(),u=ec(async()=>{const g=o==null?void 0:o.attachments.find(y=>y.name==="error-context");if(g)return await fetch(bc(o,g)).then(y=>y.text())},[o],void 0),f=U.useCallback(async g=>{var v;const y=(v=g.stack)==null?void 0:v[0];if(!y)return;let m=o?await fetch(o.createRelativeUrl(`sha1/src@${await Bb(y.file)}.txt`)):void 0;if((!m||m.status===404)&&(m=await fetch(`file?path=${encodeURIComponent(y.file)}`)),m.status>=400)return;const w=await m.text();return iT({source:w,message:Xo(g.message).split(` -`)[0]||void 0,location:y,linesAbove:100,linesBelow:100})},[o]),h=ec(()=>Y_({testInfo:(o==null?void 0:o.title)??"",metadata:l,errorContext:u,errors:(o==null?void 0:o.errorDescriptors)??[],buildCodeFrame:f}),[u,l,o,f],void 0);return n.errors.size?S.jsxs("div",{className:"fill",style:{overflow:"auto"},children:[S.jsx("span",{style:{position:"absolute",right:"5px",top:"5px",zIndex:1},children:h&&S.jsx(W_,{prompt:h})}),[...n.errors.entries()].map(([g,y])=>{const m=`error-${r}-${g}`;return S.jsx(tT,{message:g,error:y,revealInSource:i,sdkLanguage:e},m)})]}):S.jsx(ms,{text:"No errors"})};function iT({source:n,message:e,location:i,linesAbove:r,linesBelow:l}){const o=n.split(` -`).slice(),u=Math.max(0,i.line-r-1),f=Math.min(o.length,i.line+l),h=o.slice(u,f),g=String(f).length,y=h.map((m,w)=>`${u+w+1===i.line?"> ":" "}${(u+w+1).toString().padEnd(g," ")} | ${m}`);return e&&y.splice(i.line-u,0,`${" ".repeat(g+2)} | ${" ".repeat(i.column-2)} ^ ${e}`),y.join(` -`)}const sT=yc;function rT(n,e){const{entries:i}=U.useMemo(()=>{if(!n)return{entries:[]};const l=[];function o(f){var y,m,w,v,E,x;const h=l[l.length-1];h&&((y=f.browserMessage)==null?void 0:y.bodyString)===((m=h.browserMessage)==null?void 0:m.bodyString)&&((w=f.browserMessage)==null?void 0:w.location)===((v=h.browserMessage)==null?void 0:v.location)&&f.browserError===h.browserError&&((E=f.nodeMessage)==null?void 0:E.html)===((x=h.nodeMessage)==null?void 0:x.html)&&f.isError===h.isError&&f.isWarning===h.isWarning&&f.timestamp-h.timestamp<1e3?h.repeat++:l.push({...f,repeat:1})}const u=[...n.events,...n.stdio].sort((f,h)=>{const g="time"in f?f.time:f.timestamp,y="time"in h?h.time:h.timestamp;return g-y});for(const f of u){if(f.type==="console"){const h=f.args&&f.args.length?lT(f.args):Ub(f.text),g=f.location.url,m=`${g?g.substring(g.lastIndexOf("/")+1):""}:${f.location.lineNumber}`;o({browserMessage:{body:h,bodyString:f.text,location:m},isError:f.messageType==="error",isWarning:f.messageType==="warning",timestamp:f.time})}if(f.type==="event"&&f.method==="pageError"&&o({browserError:f.params.error,isError:!0,isWarning:!1,timestamp:f.time}),f.type==="stderr"||f.type==="stdout"){let h="";f.text&&(h=nl(f.text.trim())||""),f.base64&&(h=nl(atob(f.base64).trim())||""),o({nodeMessage:{html:h},isError:f.type==="stderr",isWarning:!1,timestamp:f.timestamp})}}return{entries:l}},[n]);return{entries:U.useMemo(()=>e?i.filter(l=>l.timestamp>=e.minimum&&l.timestamp<=e.maximum):i,[i,e])}}const aT=({consoleModel:n,boundaries:e,onEntryHovered:i,onAccepted:r})=>n.entries.length?S.jsx("div",{className:"console-tab",children:S.jsx(sT,{name:"console",onAccepted:r,onHighlighted:l=>i==null?void 0:i(l?n.entries.indexOf(l):void 0),items:n.entries,isError:l=>l.isError,isWarning:l=>l.isWarning,render:l=>{const o=Et(l.timestamp-e.minimum),u=S.jsx("span",{className:"console-time",children:o}),f=l.isError?"status-error":l.isWarning?"status-warning":"status-none",h=l.browserMessage||l.browserError?S.jsx("span",{className:Fe("codicon","codicon-browser",f),title:"Browser message"}):S.jsx("span",{className:Fe("codicon","codicon-file",f),title:"Runner message"});let g,y,m,w;const{browserMessage:v,browserError:E,nodeMessage:x}=l;if(v&&(g=v.location,y=v.body),E){const{error:_,value:N}=E;_?(y=_.message,w=_.stack):y=String(N)}return x&&(m=x.html),S.jsxs("div",{className:"console-line",children:[u,h,g&&S.jsx("span",{className:"console-location",children:g}),l.repeat>1&&S.jsx("span",{className:"console-repeat",children:l.repeat}),y&&S.jsx("span",{className:"console-line-message",children:y}),m&&S.jsx("span",{className:"console-line-message",dangerouslySetInnerHTML:{__html:m}}),w&&S.jsx("div",{className:"console-stack",children:w})]})}})}):S.jsx(ms,{text:"No console entries"});function lT(n){if(n.length===1)return Ub(n[0].preview);const e=typeof n[0].value=="string"&&n[0].value.includes("%"),i=e?n[0].value:"",r=e?n.slice(1):n;let l=0;const o=/%([%sdifoOc])/g;let u;const f=[];let h=[];f.push(S.jsx("span",{children:h},f.length+1));let g=0;for(;(u=o.exec(i))!==null;){const y=i.substring(g,u.index);h.push(S.jsx("span",{children:y},h.length+1)),g=u.index+2;const m=u[0][1];if(m==="%")h.push(S.jsx("span",{children:"%"},h.length+1));else if(m==="s"||m==="o"||m==="O"||m==="d"||m==="i"||m==="f"){const w=r[l++],v={};typeof(w==null?void 0:w.value)!="string"&&(v.color="var(--vscode-debugTokenExpression-number)"),h.push(S.jsx("span",{style:v,children:(w==null?void 0:w.preview)||""},h.length+1))}else if(m==="c"){h=[];const w=r[l++],v=w?oT(w.preview):{};f.push(S.jsx("span",{style:v,children:h},f.length+1))}}for(gh[1].toUpperCase());e[f]=u}return e}catch{return{}}}function cT(n){return["background","border","color","font","line","margin","padding","text"].some(i=>n.startsWith(i))}const jh=({tabs:n,selectedTab:e,setSelectedTab:i,leftToolbar:r,rightToolbar:l,dataTestId:o,mode:u})=>{const f=U.useId();return e||(e=n[0].id),u||(u="default"),S.jsx("div",{className:"tabbed-pane","data-testid":o,children:S.jsxs("div",{className:"vbox",children:[S.jsxs(nd,{children:[r&&S.jsxs("div",{style:{flex:"none",display:"flex",margin:"0 4px",alignItems:"center"},children:[...r]}),u==="default"&&S.jsx("div",{style:{flex:"auto",display:"flex",height:"100%",overflow:"hidden"},role:"tablist",children:[...n.map(h=>S.jsx(Hb,{id:h.id,ariaControls:`${f}-${h.id}`,title:h.title,count:h.count,errorCount:h.errorCount,selected:e===h.id,onSelect:i},h.id))]}),u==="select"&&S.jsx("div",{style:{flex:"auto",display:"flex",height:"100%",overflow:"hidden"},role:"tablist",children:S.jsx("select",{style:{width:"100%",background:"none",cursor:"pointer"},value:e,onChange:h=>{i==null||i(n[h.currentTarget.selectedIndex].id)},children:n.map(h=>{let g="";return h.count&&(g=` (${h.count})`),h.errorCount&&(g=` (${h.errorCount})`),S.jsxs("option",{value:h.id,role:"tab","aria-controls":`${f}-${h.id}`,children:[h.title,g]},h.id)})})}),l&&S.jsxs("div",{style:{flex:"none",display:"flex",alignItems:"center"},children:[...l]})]}),n.map(h=>{const g="tab-content tab-"+h.id;if(h.component)return S.jsx("div",{id:`${f}-${h.id}`,role:"tabpanel","aria-label":h.title,className:g,style:{display:e===h.id?"inherit":"none"},children:h.component},h.id);if(e===h.id)return S.jsx("div",{id:`${f}-${h.id}`,role:"tabpanel","aria-label":h.title,className:g,children:h.render()},h.id)})]})})},Hb=({id:n,title:e,count:i,errorCount:r,selected:l,onSelect:o,ariaControls:u})=>S.jsxs("div",{className:Fe("tabbed-pane-tab",l&&"selected"),onClick:()=>o==null?void 0:o(n),role:"tab",title:e,"aria-controls":u,"aria-selected":l,children:[S.jsx("div",{className:"tabbed-pane-tab-label",children:e}),!!i&&S.jsx("div",{className:"tabbed-pane-tab-counter",children:i}),!!r&&S.jsx("div",{className:"tabbed-pane-tab-counter error",children:r})]});async function uT(n,e){const i=navigator.platform.includes("Win")?"win":"unix";let r=[];const l=new Set(["accept-encoding","host","method","path","scheme","version","authority","protocol"]);function o(w){return'^"'+w.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`]/g,"^$&").replace(/%(?=[a-zA-Z0-9_])/g,"%^").replace(/[^ -~\r\n]/g," ").replace(/\r?\n|\r/g,`^ - -`)+'^"'}function u(w){function v(E){let _=E.charCodeAt(0).toString(16);for(;_.length<4;)_="0"+_;return"\\u"+_}return/[\0-\x1F\x7F-\x9F!]|\'/.test(w)?"$'"+w.replace(/\\/g,"\\\\").replace(/\'/g,"\\'").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\0-\x1F\x7F-\x9F!]/g,v)+"'":"'"+w+"'"}const f=i==="win"?o:u;r.push(f(e.request.url).replace(/[[{}\]]/g,"\\$&"));let h="GET";const g=[],y=await qb(n,e);y&&(g.push("--data-raw "+f(y)),l.add("content-length"),h="POST"),e.request.method!==h&&r.push("-X "+f(e.request.method));const m=e.request.headers;for(let w=0;w=3?i==="win"?` ^ - `:` \\ - `:" ")}async function fT(n,e,i=0){const r=new Set(["method","path","scheme","version","accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","date","dnt","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","via","user-agent"]),l=new Set(["cookie","authorization"]),o=JSON.stringify(e.request.url),u=e.request.headers,f=u.reduce((x,_)=>{const N=_.name;return!r.has(N.toLowerCase())&&!N.includes(":")&&x.append(N,_.value),x},new Headers),h={};for(const x of f)h[x[0]]=x[1];const g=e.request.cookies.length||u.some(({name:x})=>l.has(x.toLowerCase()))?"include":"omit",y=u.find(({name:x})=>x.toLowerCase()==="referer"),m=y?y.value:void 0,w=await qb(n,e),v={headers:Object.keys(h).length?h:void 0,referrer:m,body:w,method:e.request.method,mode:"cors"};if(i===1){const x=u.find(N=>N.name.toLowerCase()==="cookie"),_={};delete v.mode,x&&(_.cookie=x.value),m&&(delete v.referrer,_.Referer=m),Object.keys(_).length&&(v.headers={...h,..._})}else v.credentials=g;const E=JSON.stringify(v,null,2);return`fetch(${o}, ${E});`}async function qb(n,e){var i,r;return n&&((i=e.request.postData)!=null&&i._sha1)?await fetch(n.createRelativeUrl(`sha1/${e.request.postData._sha1}`)).then(l=>l.text()):(r=e.request.postData)==null?void 0:r.text}class hT{generatePlaywrightRequestCall(e,i){let r=e.method.toLowerCase();const l=new URL(e.url),o=`${l.origin}${l.pathname}`,u={};["delete","get","head","post","put","patch"].includes(r)||(u.method=r,r="fetch"),l.searchParams.size&&(u.params=Object.fromEntries(l.searchParams.entries())),i&&(u.data=i),e.headers.length&&(u.headers=Object.fromEntries(e.headers.map(g=>[g.name,g.value])));const f=[`'${o}'`];return Object.keys(u).length>0&&f.push(this.prettyPrintObject(u)),`await page.request.${r}(${f.join(", ")});`}prettyPrintObject(e,i=2,r=0){if(e===null)return"null";if(e===void 0)return"undefined";if(typeof e!="object")return typeof e=="string"?this.stringLiteral(e):String(e);if(Array.isArray(e)){if(e.length===0)return"[]";const f=" ".repeat(r*i),h=" ".repeat((r+1)*i);return`[ -${e.map(y=>`${h}${this.prettyPrintObject(y,i,r+1)}`).join(`, -`)} -${f}]`}if(Object.keys(e).length===0)return"{}";const l=" ".repeat(r*i),o=" ".repeat((r+1)*i);return`{ -${Object.entries(e).map(([f,h])=>{const g=this.prettyPrintObject(h,i,r+1),y=/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(f)?f:this.stringLiteral(f);return`${o}${y}: ${g}`}).join(`, -`)} -${l}}`}stringLiteral(e){return e=e.replace(/\\/g,"\\\\").replace(/'/g,"\\'"),e.includes(` -`)||e.includes("\r")||e.includes(" ")?"`"+e+"`":`'${e}'`}}class dT{generatePlaywrightRequestCall(e,i){const r=new URL(e.url),o=[`"${`${r.origin}${r.pathname}`}"`];let u=e.method.toLowerCase();["delete","get","head","post","put","patch"].includes(u)||(o.push(`method="${u}"`),u="fetch"),r.searchParams.size&&o.push(`params=${this.prettyPrintObject(Object.fromEntries(r.searchParams.entries()))}`),i&&o.push(`data=${this.prettyPrintObject(i)}`),e.headers.length&&o.push(`headers=${this.prettyPrintObject(Object.fromEntries(e.headers.map(h=>[h.name,h.value])))}`);const f=o.length===1?o[0]:` -${o.map(h=>this.indent(h,2)).join(`, -`)} -`;return`await page.request.${u}(${f})`}indent(e,i){return e.split(` -`).map(r=>" ".repeat(i)+r).join(` -`)}prettyPrintObject(e,i=2,r=0){if(e===null||e===void 0)return"None";if(typeof e!="object")return typeof e=="string"?this.stringLiteral(e):typeof e=="boolean"?e?"True":"False":String(e);if(Array.isArray(e)){if(e.length===0)return"[]";const f=" ".repeat(r*i),h=" ".repeat((r+1)*i);return`[ -${e.map(y=>`${h}${this.prettyPrintObject(y,i,r+1)}`).join(`, -`)} -${f}]`}if(Object.keys(e).length===0)return"{}";const l=" ".repeat(r*i),o=" ".repeat((r+1)*i);return`{ -${Object.entries(e).map(([f,h])=>{const g=this.prettyPrintObject(h,i,r+1);return`${o}${this.stringLiteral(f)}: ${g}`}).join(`, -`)} -${l}}`}stringLiteral(e){return JSON.stringify(e)}}class pT{generatePlaywrightRequestCall(e,i){const r=new URL(e.url),l=`${r.origin}${r.pathname}`,o={},u=[];let f=e.method.toLowerCase();["delete","get","head","post","put","patch"].includes(f)||(o.Method=f,f="fetch"),r.searchParams.size&&(o.Params=Object.fromEntries(r.searchParams.entries())),i&&(o.Data=i),e.headers.length&&(o.Headers=Object.fromEntries(e.headers.map(y=>[y.name,y.value])));const h=[`"${l}"`];return Object.keys(o).length>0&&h.push(this.prettyPrintObject(o)),`${u.join(` -`)}${u.length?` -`:""}await request.${this.toFunctionName(f)}(${h.join(", ")});`}toFunctionName(e){return e[0].toUpperCase()+e.slice(1)+"Async"}prettyPrintObject(e,i=2,r=0){if(e===null||e===void 0)return"null";if(typeof e!="object")return typeof e=="string"?this.stringLiteral(e):typeof e=="boolean"?e?"true":"false":String(e);if(Array.isArray(e)){if(e.length===0)return"new object[] {}";const f=" ".repeat(r*i),h=" ".repeat((r+1)*i);return`new object[] { -${e.map(y=>`${h}${this.prettyPrintObject(y,i,r+1)}`).join(`, -`)} -${f}}`}if(Object.keys(e).length===0)return"new {}";const l=" ".repeat(r*i),o=" ".repeat((r+1)*i);return`new() { -${Object.entries(e).map(([f,h])=>{const g=this.prettyPrintObject(h,i,r+1),y=r===0?f:`[${this.stringLiteral(f)}]`;return`${o}${y} = ${g}`}).join(`, -`)} -${l}}`}stringLiteral(e){return JSON.stringify(e)}}class gT{generatePlaywrightRequestCall(e,i){const r=new URL(e.url),l=[`"${r.origin}${r.pathname}"`],o=[];let u=e.method.toLowerCase();["delete","get","head","post","put","patch"].includes(u)||(o.push(`setMethod("${u}")`),u="fetch");for(const[f,h]of r.searchParams)o.push(`setQueryParam(${this.stringLiteral(f)}, ${this.stringLiteral(h)})`);i&&o.push(`setData(${this.stringLiteral(i)})`);for(const f of e.headers)o.push(`setHeader(${this.stringLiteral(f.name)}, ${this.stringLiteral(f.value)})`);return o.length>0&&l.push(`RequestOptions.create() - .${o.join(` - .`)} -`),`request.${u}(${l.join(", ")});`}stringLiteral(e){return JSON.stringify(e)}}function mT(n){if(n==="javascript")return new hT;if(n==="python")return new dT;if(n==="csharp")return new pT;if(n==="java")return new gT;throw new Error("Unsupported language: "+n)}const yT=({resource:n,sdkLanguage:e,startTimeOffset:i,onClose:r})=>{const[l,o]=U.useState("headers"),u=ti(),f=ec(async()=>{if(u&&n.request.postData){const h=n.request.headers.find(y=>y.name.toLowerCase()==="content-type"),g=h?h.value:"";if(n.request.postData._sha1){const y=await fetch(u.createRelativeUrl(`sha1/${n.request.postData._sha1}`));return{text:Lh(await y.text(),g),mimeType:g}}else return{text:Lh(n.request.postData.text,g),mimeType:g}}else return null},[n],null);return S.jsx(jh,{leftToolbar:[S.jsx(Ht,{icon:"close",title:"Close",onClick:r},"close")],rightToolbar:[S.jsx(bT,{requestBody:f,resource:n,sdkLanguage:e},"dropdown")],tabs:[{id:"headers",title:"Headers",render:()=>S.jsx(vT,{resource:n,startTimeOffset:i})},{id:"payload",title:"Payload",render:()=>S.jsx(ST,{resource:n,requestBody:f})},{id:"response",title:"Response",render:()=>S.jsx(wT,{resource:n})}],selectedTab:l,setSelectedTab:o})},bT=({resource:n,sdkLanguage:e,requestBody:i})=>{const r=ti(),l=S.jsxs(S.Fragment,{children:[S.jsx("span",{className:"codicon codicon-check",style:{marginRight:"5px"}})," Copied "]}),o=async()=>mT(e).generatePlaywrightRequestCall(n.request,i==null?void 0:i.text);return S.jsxs("div",{className:"copy-request-dropdown",children:[S.jsxs(Ht,{className:"copy-request-dropdown-toggle",children:[S.jsx("span",{className:"codicon codicon-copy",style:{marginRight:"5px"}}),"Copy request",S.jsx("span",{className:"codicon codicon-chevron-down",style:{marginLeft:"5px"}})]}),S.jsxs("div",{className:"copy-request-dropdown-menu",children:[S.jsx(Ko,{description:"Copy as cURL",copiedDescription:l,value:()=>uT(r,n)}),S.jsx(Ko,{description:"Copy as Fetch",copiedDescription:l,value:()=>fT(r,n)}),S.jsx(Ko,{description:"Copy as Playwright",copiedDescription:l,value:o})]})]})},Xa=({title:n,data:e,showCount:i,children:r,className:l})=>{const[o,u]=on(`trace-viewer-network-details-${n.replaceAll(" ","-")}`,!0);return S.jsxs(Rb,{expanded:o,setExpanded:u,expandOnTitleClick:!0,title:S.jsxs("span",{className:"network-request-details-header",children:[n,i&&S.jsxs("span",{className:"network-request-details-header-count",children:[" × ",(e==null?void 0:e.length)??0]})]}),className:l,children:[e&&S.jsx("table",{className:"network-request-details-table",children:S.jsx("tbody",{children:e.map(({name:f,value:h},g)=>h!==null&&S.jsxs("tr",{children:[S.jsx("td",{children:f}),S.jsx("td",{children:h})]},g))})}),r]})},vT=({resource:n,startTimeOffset:e})=>{const i=U.useMemo(()=>Object.entries({URL:n.request.url,Method:n.request.method,"Status Code":n.response.status!==-1&&S.jsxs("span",{className:_T(n.response.status),children:[" ",n.response.status," ",n.response.statusText]}),Start:Et(e),Duration:Et(n.time)}).map(([r,l])=>({name:r,value:l})),[n,e]);return S.jsxs("div",{className:"vbox network-request-details-tab",children:[S.jsx(Xa,{title:"General",data:i}),S.jsx(Xa,{title:"Request Headers",showCount:!0,data:n.request.headers}),S.jsx(Xa,{title:"Response Headers",showCount:!0,data:n.response.headers})]})},ST=({resource:n,requestBody:e})=>S.jsxs("div",{className:"vbox network-request-details-tab",children:[n.request.queryString.length===0&&!e&&S.jsx("em",{className:"network-request-no-payload",children:"No payload for this request."}),n.request.queryString.length>0&&S.jsx(Xa,{title:"Query String Parameters",showCount:!0,data:n.request.queryString}),e&&S.jsx(Xa,{title:"Request Body",className:"network-request-request-body",children:S.jsx(xr,{text:e.text,mimeType:e.mimeType,readOnly:!0,lineNumbers:!0})})]}),wT=({resource:n})=>{const e=ti(),[i,r]=U.useState(null);return U.useEffect(()=>{(async()=>{if(e&&n.response.content._sha1){const o=n.response.content.mimeType.includes("image"),u=n.response.content.mimeType.includes("font"),f=await fetch(e.createRelativeUrl(`sha1/${n.response.content._sha1}`));if(o){const h=await f.blob(),g=new FileReader,y=new Promise(m=>g.onload=m);g.readAsDataURL(h),r({dataUrl:(await y).target.result})}else if(u){const h=await f.arrayBuffer();r({font:h})}else{const h=Lh(await f.text(),n.response.content.mimeType);r({text:h,mimeType:n.response.content.mimeType})}}else r(null)})()},[n,e]),S.jsxs("div",{className:"vbox network-request-details-tab",children:[!n.response.content._sha1&&S.jsx("div",{children:"Response body is not available for this request."}),i&&i.font&&S.jsx(xT,{font:i.font}),i&&i.dataUrl&&S.jsx("div",{children:S.jsx("img",{draggable:"false",src:i.dataUrl})}),i&&i.text&&S.jsx(xr,{text:i.text,mimeType:i.mimeType,readOnly:!0,lineNumbers:!0})]})},xT=({font:n})=>{const[e,i]=U.useState(!1);return U.useEffect(()=>{let r;try{r=new FontFace("font-preview",n),r.status==="loaded"&&document.fonts.add(r),r.status==="error"&&i(!0)}catch{i(!0)}return()=>{document.fonts.delete(r)}},[n]),e?S.jsx("div",{className:"network-font-preview-error",children:"Could not load font preview"}):S.jsxs("div",{className:"network-font-preview",children:["ABCDEFGHIJKLM",S.jsx("br",{}),"NOPQRSTUVWXYZ",S.jsx("br",{}),"abcdefghijklm",S.jsx("br",{}),"nopqrstuvwxyz",S.jsx("br",{}),"1234567890"]})};function _T(n){return n<300||n===304?"green-circle":n<400?"yellow-circle":"red-circle"}function Lh(n,e){if(n===null)return"Loading...";const i=n;if(i==="")return"";if(H_(e))try{return JSON.stringify(JSON.parse(i),null,2)}catch{return i}return e.includes("application/x-www-form-urlencoded")?decodeURIComponent(i):i}function TT(n){const[e,i]=U.useState([]);U.useEffect(()=>{const o=[];for(let u=0;u{var u,f;(f=n.setSorting)==null||f.call(n,{by:o,negate:((u=n.sorting)==null?void 0:u.by)===o?!n.sorting.negate:!1})},[n]);return S.jsxs("div",{className:`grid-view ${n.name}-grid-view`,children:[S.jsx(Lb,{orientation:"horizontal",offsets:e,setOffsets:r,resizerColor:"var(--vscode-panel-border)",resizerWidth:1,minColumnWidth:25}),S.jsxs("div",{className:"vbox",children:[S.jsx("div",{className:"grid-view-header",children:n.columns.map((o,u)=>S.jsxs("div",{className:"grid-view-header-cell "+ET(o,n.sorting),style:{width:un.setSorting&&l(o),children:[S.jsx("span",{className:"grid-view-header-cell-title",children:n.columnTitle(o)}),S.jsx("span",{className:"codicon codicon-triangle-up"}),S.jsx("span",{className:"codicon codicon-triangle-down"})]},n.columnTitle(o)))}),S.jsx(yc,{name:n.name,items:n.items,ariaLabel:n.ariaLabel,id:n.id,render:(o,u)=>S.jsx(S.Fragment,{children:n.columns.map((f,h)=>{const{body:g,title:y}=n.render(o,f,u);return S.jsx("div",{className:`grid-view-cell grid-view-column-${String(f)}`,title:y,style:{width:hS.jsxs("div",{className:"network-filters",children:[S.jsx("input",{type:"search",placeholder:"Filter network",spellCheck:!1,value:n.searchValue,onChange:i=>e({...n,searchValue:i.target.value})}),S.jsxs("div",{className:"network-filters-resource-types",role:"tablist","aria-multiselectable":"true",children:[S.jsx("div",{title:"All",onClick:()=>e({...n,resourceTypes:new Set}),className:`network-filters-resource-type ${n.resourceTypes.size===0?"selected":""}`,children:"All"}),AT.map(i=>S.jsx("div",{title:i,onClick:r=>{let l;r.ctrlKey||r.metaKey?l=n.resourceTypes.symmetricDifference(new Set([i])):l=new Set([i]),e({...n,resourceTypes:l})},className:`network-filters-resource-type ${n.resourceTypes.has(i)?"selected":""}`,role:"tab","aria-selected":n.resourceTypes.has(i),children:i},i))]})]}),kT=TT;function MT(n,e){const i=U.useMemo(()=>((n==null?void 0:n.resources)||[]).filter(u=>e?!!u._monotonicTime&&u._monotonicTime>=e.minimum&&u._monotonicTime<=e.maximum:!0),[n,e]),r=U.useMemo(()=>new zT(n),[n]);return{resources:i,contextIdMap:r}}const OT=({boundaries:n,networkModel:e,onResourceHovered:i,sdkLanguage:r})=>{const[l,o]=U.useState(void 0),[u,f]=U.useState(void 0),[h,g]=U.useState(NT),y=U.useMemo(()=>u&&e.resources.includes(u.resource)?u:void 0,[u,e.resources]),{renderedEntries:m}=U.useMemo(()=>{const _=e.resources.map((N,C)=>BT(N,n,e.contextIdMap,C)).filter(IT(h));return l&&HT(_,l),{renderedEntries:_}},[e.resources,e.contextIdMap,h,l,n]),[w,v]=U.useState(()=>new Map($b().map(_=>[_,LT(_)]))),E=U.useCallback(_=>{g(_),f(void 0)},[]);if(!e.resources.length)return S.jsx(ms,{text:"No network calls"});const x=S.jsx(kT,{name:"network",ariaLabel:"Network requests",items:m,selectedItem:y,onSelected:_=>f(_),onHighlighted:_=>i==null?void 0:i(_==null?void 0:_.ordinal),columns:RT(!!y,m),columnTitle:jT,columnWidths:w,setColumnWidths:v,isError:_=>_.status.code>=400||_.status.code===-1,isInfo:_=>!!_.route,render:(_,N)=>DT(_,N),sorting:l,setSorting:o});return S.jsxs(S.Fragment,{children:[S.jsx(CT,{filterState:h,onFilterStateChange:E}),!y&&x,y&&S.jsx(nc,{sidebarSize:w.get("name"),sidebarIsFirst:!0,orientation:"horizontal",settingName:"networkResourceDetails",main:S.jsx(yT,{resource:y.resource,sdkLanguage:r,startTimeOffset:y.start,onClose:()=>f(void 0)}),sidebar:x})]})},jT=n=>n==="contextId"?"Source":n==="name"?"Name":n==="method"?"Method":n==="status"?"Status":n==="contentType"?"Content Type":n==="duration"?"Duration":n==="size"?"Size":n==="start"?"Start":n==="route"?"Route":"",LT=n=>n==="name"?200:n==="method"||n==="status"?60:n==="contentType"?200:n==="contextId"?60:100;function RT(n,e){if(n){const r=["name"];return h0(e)&&r.unshift("contextId"),r}let i=$b();return h0(e)||(i=i.filter(r=>r!=="contextId")),i}function $b(){return["contextId","name","method","status","contentType","duration","size","start","route"]}const DT=(n,e)=>e==="contextId"?{body:n.contextId,title:n.name.url}:e==="name"?{body:n.name.name,title:n.name.url}:e==="method"?{body:n.method}:e==="status"?{body:n.status.code>0?n.status.code:"",title:n.status.text}:e==="contentType"?{body:n.contentType}:e==="duration"?{body:Et(n.duration)}:e==="size"?{body:fx(n.size)}:e==="start"?{body:Et(n.start)}:e==="route"?{body:n.route}:{body:""};class zT{constructor(e){Ma(this,"_pagerefToShortId",new Map);Ma(this,"_contextToId",new Map);Ma(this,"_lastPageId",0);Ma(this,"_lastApiRequestContextId",0)}contextId(e){return e.pageref?this._pageId(e.pageref):e._apiRequest?this._apiRequestContextId(e):""}_pageId(e){let i=this._pagerefToShortId.get(e);return i||(++this._lastPageId,i="page#"+this._lastPageId,this._pagerefToShortId.set(e,i)),i}_apiRequestContextId(e){const i=rb(e);if(!i)return"";let r=this._contextToId.get(i);return r||(++this._lastApiRequestContextId,r="api#"+this._lastApiRequestContextId,this._contextToId.set(i,r)),r}}function h0(n){const e=new Set;for(const i of n)if(e.add(i.contextId),e.size>1)return!0;return!1}const BT=(n,e,i,r)=>{const l=UT(n);let o;try{const h=new URL(n.request.url);o=h.pathname.substring(h.pathname.lastIndexOf("/")+1),o||(o=h.host),h.search&&(o+=h.search)}catch{o=n.request.url}let u=n.response.content.mimeType;const f=u.match(/^(.*);\s*charset=.*$/);return f&&(u=f[1]),{ordinal:r,name:{name:o,url:n.request.url},method:n.request.method,status:{code:n.response.status,text:n.response.statusText},contentType:u,duration:n.time,size:n.response._transferSize>0?n.response._transferSize:n.response.bodySize,start:n._monotonicTime-e.minimum,route:l,resource:n,contextId:i.contextId(n)}};function UT(n){return n._wasAborted?"aborted":n._wasContinued?"continued":n._wasFulfilled?"fulfilled":n._apiRequest?"api":""}function HT(n,e){const i=qT(e==null?void 0:e.by);i&&n.sort(i),e.negate&&n.reverse()}function qT(n){if(n==="start")return(e,i)=>e.start-i.start;if(n==="duration")return(e,i)=>e.duration-i.duration;if(n==="status")return(e,i)=>e.status.code-i.status.code;if(n==="method")return(e,i)=>{const r=e.method,l=i.method;return r.localeCompare(l)};if(n==="size")return(e,i)=>e.size-i.size;if(n==="contentType")return(e,i)=>e.contentType.localeCompare(i.contentType);if(n==="name")return(e,i)=>e.name.name.localeCompare(i.name.name);if(n==="route")return(e,i)=>e.route.localeCompare(i.route);if(n==="contextId")return(e,i)=>e.contextId.localeCompare(i.contextId)}const $T={Fetch:n=>n==="application/json",HTML:n=>n==="text/html",CSS:n=>n==="text/css",JS:n=>n.includes("javascript"),Font:n=>n.includes("font"),Image:n=>n.includes("image")};function IT({searchValue:n,resourceTypes:e}){return i=>(e.size===0||Array.from(e).some(l=>$T[l](i.contentType)))&&i.name.url.toLowerCase().includes(n.toLowerCase())}function VT(n,e){if(n.role!==e.role||n.name!==e.name||!GT(n,e)||lc(n)!==lc(e))return!1;const i=Object.keys(n.props),r=Object.keys(e.props);return i.length===r.length&&i.every(l=>n.props[l]===e.props[l])}function lc(n){return n.box.cursor==="pointer"}function GT(n,e){return n.active===e.active&&n.checked===e.checked&&n.disabled===e.disabled&&n.expanded===e.expanded&&n.selected===e.selected&&n.level===e.level&&n.pressed===e.pressed}function id(n,e,i={}){var w;const r=new n.LineCounter,l={keepSourceTokens:!0,lineCounter:r,...i},o=n.parseDocument(e,l),u=[],f=v=>[r.linePos(v[0]),r.linePos(v[1])],h=v=>{u.push({message:v.message,range:[r.linePos(v.pos[0]),r.linePos(v.pos[1])]})},g=(v,E)=>{for(const x of E.items){if(x instanceof n.Scalar&&typeof x.value=="string"){const C=oc.parse(x,l,u);C&&(v.children=v.children||[],v.children.push(C));continue}if(x instanceof n.YAMLMap){y(v,x);continue}u.push({message:"Sequence items should be strings or maps",range:f(x.range||E.range)})}},y=(v,E)=>{for(const x of E.items){if(v.children=v.children||[],!(x.key instanceof n.Scalar&&typeof x.key.value=="string")){u.push({message:"Only string keys are supported",range:f(x.key.range||E.range)});continue}const N=x.key,C=x.value;if(N.value==="text"){if(!(C instanceof n.Scalar&&typeof C.value=="string")){u.push({message:"Text value should be a string",range:f(x.value.range||E.range)});continue}v.children.push({kind:"text",text:oh(C.value)});continue}if(N.value==="/children"){if(!(C instanceof n.Scalar&&typeof C.value=="string")||C.value!=="contain"&&C.value!=="equal"&&C.value!=="deep-equal"){u.push({message:'Strict value should be "contain", "equal" or "deep-equal"',range:f(x.value.range||E.range)});continue}v.containerMode=C.value;continue}if(N.value.startsWith("/")){if(!(C instanceof n.Scalar&&typeof C.value=="string")){u.push({message:"Property value should be a string",range:f(x.value.range||E.range)});continue}v.props=v.props??{},v.props[N.value.slice(1)]=oh(C.value);continue}const $=oc.parse(N,l,u);if(!$)continue;if(C instanceof n.Scalar){const K=typeof C.value;if(K!=="string"&&K!=="number"&&K!=="boolean"){u.push({message:"Node value should be a string or a sequence",range:f(x.value.range||E.range)});continue}v.children.push({...$,children:[{kind:"text",text:oh(String(C.value))}]});continue}if(C instanceof n.YAMLSeq){v.children.push($),g($,C);continue}u.push({message:"Map values should be strings or sequences",range:f(x.value.range||E.range)})}},m={kind:"role",role:"fragment"};return o.errors.forEach(h),u.length?{errors:u,fragment:m}:(o.contents instanceof n.YAMLSeq||u.push({message:'Aria snapshot must be a YAML sequence, elements starting with " -"',range:o.contents?f(o.contents.range):[{line:0,col:0},{line:0,col:0}]}),u.length?{errors:u,fragment:m}:(g(m,o.contents),u.length?{errors:u,fragment:KT}:((w=m.children)==null?void 0:w.length)===1&&(!m.containerMode||m.containerMode==="contain")?{fragment:m.children[0],errors:[]}:{fragment:m,errors:[]}))}const KT={kind:"role",role:"fragment"};function Ib(n){return n.replace(/[\u200b\u00ad]/g,"").replace(/[\r\n\s\t]+/g," ").trim()}function oh(n){return{raw:n,normalized:Ib(n)}}class oc{static parse(e,i,r){try{return new oc(e.value)._parse()}catch(l){if(l instanceof d0){const o=i.prettyErrors===!1?l.message:l.message+`: - -`+e.value+` -`+" ".repeat(l.pos)+`^ -`;return r.push({message:o,range:[i.lineCounter.linePos(e.range[0]),i.lineCounter.linePos(e.range[0]+l.pos)]}),null}throw l}}constructor(e){this._input=e,this._pos=0,this._length=e.length}_peek(){return this._input[this._pos]||""}_next(){return this._pos=this._length}_isWhitespace(){return!this._eof()&&/\s/.test(this._peek())}_skipWhitespace(){for(;this._isWhitespace();)this._pos++}_readIdentifier(e){this._eof()&&this._throwError(`Unexpected end of input when expecting ${e}`);const i=this._pos;for(;!this._eof()&&/[a-zA-Z]/.test(this._peek());)this._pos++;return this._input.slice(i,this._pos)}_readString(){let e="",i=!1;for(;!this._eof();){const r=this._next();if(i)e+=r,i=!1;else if(r==="\\")i=!0;else{if(r==='"')return e;e+=r}}this._throwError("Unterminated string")}_throwError(e,i=0){throw new d0(e,i||this._pos)}_readRegex(){let e="",i=!1,r=!1;for(;!this._eof();){const l=this._next();if(i)e+=l,i=!1;else if(l==="\\")i=!0,e+=l;else{if(l==="/"&&!r)return{pattern:e};l==="["?(r=!0,e+=l):l==="]"&&r?(e+=l,r=!1):e+=l}}this._throwError("Unterminated regex")}_readStringOrRegex(){const e=this._peek();return e==='"'?(this._next(),Ib(this._readString())):e==="/"?(this._next(),this._readRegex()):null}_readAttributes(e){let i=this._pos;for(;this._skipWhitespace(),this._peek()==="[";){this._next(),this._skipWhitespace(),i=this._pos;const r=this._readIdentifier("attribute");this._skipWhitespace();let l="";if(this._peek()==="=")for(this._next(),this._skipWhitespace(),i=this._pos;this._peek()!=="]"&&!this._isWhitespace()&&!this._eof();)l+=this._next();this._skipWhitespace(),this._peek()!=="]"&&this._throwError("Expected ]"),this._next(),this._applyAttribute(e,r,l||"true",i)}}_parse(){this._skipWhitespace();const e=this._readIdentifier("role");this._skipWhitespace();const i=this._readStringOrRegex()||"",r={kind:"role",role:e,name:i};return this._readAttributes(r),this._skipWhitespace(),this._eof()||this._throwError("Unexpected input"),r}_applyAttribute(e,i,r,l){if(i==="checked"){this._assert(r==="true"||r==="false"||r==="mixed",'Value of "checked" attribute must be a boolean or "mixed"',l),e.checked=r==="true"?!0:r==="false"?!1:"mixed";return}if(i==="disabled"){this._assert(r==="true"||r==="false",'Value of "disabled" attribute must be a boolean',l),e.disabled=r==="true";return}if(i==="expanded"){this._assert(r==="true"||r==="false",'Value of "expanded" attribute must be a boolean',l),e.expanded=r==="true";return}if(i==="active"){this._assert(r==="true"||r==="false",'Value of "active" attribute must be a boolean',l),e.active=r==="true";return}if(i==="level"){this._assert(!isNaN(Number(r)),'Value of "level" attribute must be a number',l),e.level=Number(r);return}if(i==="pressed"){this._assert(r==="true"||r==="false"||r==="mixed",'Value of "pressed" attribute must be a boolean or "mixed"',l),e.pressed=r==="true"?!0:r==="false"?!1:"mixed";return}if(i==="selected"){this._assert(r==="true"||r==="false",'Value of "selected" attribute must be a boolean',l),e.selected=r==="true";return}this._assert(!1,`Unsupported attribute [${i}]`,l)}_assert(e,i,r){e||this._throwError(i||"Assertion error",r)}}class d0 extends Error{constructor(e,i){super(e),this.pos=i}}function YT(n,e){var u,f;function i(h,g,y){let m=1,w=y+m;for(const v of h.children||[])typeof v=="string"?(m++,w++):(m+=i(v,g,w),w+=m);if(!["none","presentation","fragment","iframe","generic"].includes(h.role)&&h.name){let v=g.get(h.role);v||(v=new Map,g.set(h.role,v));const E=v.get(h.name),x=m*100-y;(!E||E.sizeAndPositiong.sizeAndPosition-h.sizeAndPosition),(f=o[0])==null?void 0:f.node}function XT(n){return Vb(n)?"'"+n.replace(/'/g,"''")+"'":n}function ch(n){return Vb(n)?'"'+n.replace(/[\\"\x00-\x1f\x7f-\x9f]/g,e=>{switch(e){case"\\":return"\\\\";case'"':return'\\"';case"\b":return"\\b";case"\f":return"\\f";case` -`:return"\\n";case"\r":return"\\r";case" ":return"\\t";default:return"\\x"+e.charCodeAt(0).toString(16).padStart(2,"0")}})+'"':n}function Vb(n){return!!(n.length===0||/^\s|\s$/.test(n)||/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(n)||/^-/.test(n)||/[\n:](\s|$)/.test(n)||/\s#/.test(n)||/[\n\r]/.test(n)||/^[&*\],?!>|@"'#%]/.test(n)||/[{}`]/.test(n)||/^\[/.test(n)||!isNaN(Number(n))||["y","n","yes","no","true","false","on","off","null"].includes(n.toLowerCase()))}let Gb={};function FT(n){Gb=n}function il(n,e){for(;e;){if(n.contains(e))return!0;e=Yb(e)}return!1}function bt(n){if(n.parentElement)return n.parentElement;if(n.parentNode&&n.parentNode.nodeType===11&&n.parentNode.host)return n.parentNode.host}function Kb(n){let e=n;for(;e.parentNode;)e=e.parentNode;if(e.nodeType===11||e.nodeType===9)return e}function Yb(n){for(;n.parentElement;)n=n.parentElement;return bt(n)}function $a(n,e,i){for(;n;){const r=n.closest(e);if(i&&r!==i&&(r!=null&&r.contains(i)))return;if(r)return r;n=Yb(n)}}function zi(n,e){const i=e==="::before"?rd:e==="::after"?ad:sd;if(i&&i.has(n))return i.get(n);const r=n.ownerDocument&&n.ownerDocument.defaultView?n.ownerDocument.defaultView.getComputedStyle(n,e):void 0;return i==null||i.set(n,r),r}function Xb(n,e){if(e=e??zi(n),!e)return!0;if(Element.prototype.checkVisibility&&Gb.browserNameForWorkarounds!=="webkit"){if(!n.checkVisibility())return!1}else{const i=n.closest("details,summary");if(i!==n&&(i==null?void 0:i.nodeName)==="DETAILS"&&!i.open)return!1}return e.visibility==="visible"}function cc(n){const e=zi(n);if(!e)return{visible:!0,inline:!1};const i=e.cursor;if(e.display==="contents"){for(let l=n.firstChild;l;l=l.nextSibling){if(l.nodeType===1&&ji(l))return{visible:!0,inline:!1,cursor:i};if(l.nodeType===3&&Fb(l))return{visible:!0,inline:!0,cursor:i}}return{visible:!1,inline:!1,cursor:i}}if(!Xb(n,e))return{cursor:i,visible:!1,inline:!1};const r=n.getBoundingClientRect();return{cursor:i,visible:r.width>0&&r.height>0,inline:e.display==="inline"}}function ji(n){return cc(n).visible}function Fb(n){const e=n.ownerDocument.createRange();e.selectNode(n);const i=e.getBoundingClientRect();return i.width>0&&i.height>0}function Xe(n){const e=n.tagName;return typeof e=="string"?e.toUpperCase():n instanceof HTMLFormElement?"FORM":n.tagName.toUpperCase()}let sd,rd,ad,Qb=0;function ld(){++Qb,sd??(sd=new Map),rd??(rd=new Map),ad??(ad=new Map)}function od(){--Qb||(sd=void 0,rd=void 0,ad=void 0)}function p0(n){return n.hasAttribute("aria-label")||n.hasAttribute("aria-labelledby")}const g0="article:not([role]), aside:not([role]), main:not([role]), nav:not([role]), section:not([role]), [role=article], [role=complementary], [role=main], [role=navigation], [role=region]",QT=[["aria-atomic",void 0],["aria-busy",void 0],["aria-controls",void 0],["aria-current",void 0],["aria-describedby",void 0],["aria-details",void 0],["aria-dropeffect",void 0],["aria-flowto",void 0],["aria-grabbed",void 0],["aria-hidden",void 0],["aria-keyshortcuts",void 0],["aria-label",["caption","code","deletion","emphasis","generic","insertion","paragraph","presentation","strong","subscript","superscript"]],["aria-labelledby",["caption","code","deletion","emphasis","generic","insertion","paragraph","presentation","strong","subscript","superscript"]],["aria-live",void 0],["aria-owns",void 0],["aria-relevant",void 0],["aria-roledescription",["generic"]]];function Jb(n,e){return QT.some(([i,r])=>!(r!=null&&r.includes(e||""))&&n.hasAttribute(i))}function Pb(n){return!Number.isNaN(Number(String(n.getAttribute("tabindex"))))}function JT(n){return!cv(n)&&(PT(n)||Pb(n))}function PT(n){const e=Xe(n);return["BUTTON","DETAILS","SELECT","TEXTAREA"].includes(e)?!0:e==="A"||e==="AREA"?n.hasAttribute("href"):e==="INPUT"?!n.hidden:!1}const uh={A:n=>n.hasAttribute("href")?"link":null,AREA:n=>n.hasAttribute("href")?"link":null,ARTICLE:()=>"article",ASIDE:()=>"complementary",BLOCKQUOTE:()=>"blockquote",BUTTON:()=>"button",CAPTION:()=>"caption",CODE:()=>"code",DATALIST:()=>"listbox",DD:()=>"definition",DEL:()=>"deletion",DETAILS:()=>"group",DFN:()=>"term",DIALOG:()=>"dialog",DT:()=>"term",EM:()=>"emphasis",FIELDSET:()=>"group",FIGURE:()=>"figure",FOOTER:n=>$a(n,g0)?null:"contentinfo",FORM:n=>p0(n)?"form":null,H1:()=>"heading",H2:()=>"heading",H3:()=>"heading",H4:()=>"heading",H5:()=>"heading",H6:()=>"heading",HEADER:n=>$a(n,g0)?null:"banner",HR:()=>"separator",HTML:()=>"document",IMG:n=>n.getAttribute("alt")===""&&!n.getAttribute("title")&&!Jb(n)&&!Pb(n)?"presentation":"img",INPUT:n=>{const e=n.type.toLowerCase();if(e==="search")return n.hasAttribute("list")?"combobox":"searchbox";if(["email","tel","text","url",""].includes(e)){const i=Nr(n,n.getAttribute("list"))[0];return i&&Xe(i)==="DATALIST"?"combobox":"textbox"}return e==="hidden"?null:e==="file"?"button":dE[e]||"textbox"},INS:()=>"insertion",LI:()=>"listitem",MAIN:()=>"main",MARK:()=>"mark",MATH:()=>"math",MENU:()=>"list",METER:()=>"meter",NAV:()=>"navigation",OL:()=>"list",OPTGROUP:()=>"group",OPTION:()=>"option",OUTPUT:()=>"status",P:()=>"paragraph",PROGRESS:()=>"progressbar",SEARCH:()=>"search",SECTION:n=>p0(n)?"region":null,SELECT:n=>n.hasAttribute("multiple")||n.size>1?"listbox":"combobox",STRONG:()=>"strong",SUB:()=>"subscript",SUP:()=>"superscript",SVG:()=>"img",TABLE:()=>"table",TBODY:()=>"rowgroup",TD:n=>{const e=$a(n,"table"),i=e?cd(e):"";return i==="grid"||i==="treegrid"?"gridcell":"cell"},TEXTAREA:()=>"textbox",TFOOT:()=>"rowgroup",TH:n=>{const e=n.getAttribute("scope");if(e==="col"||e==="colgroup")return"columnheader";if(e==="row"||e==="rowgroup")return"rowheader";const i=n.nextElementSibling,r=n.previousElementSibling,l=n.parentElement&&Xe(n.parentElement)==="TR"?n.parentElement:void 0;if(!i&&!r){if(l){const o=$a(l,"table");if(o&&o.rows.length<=1)return null}return"columnheader"}return m0(i)&&m0(r)?"columnheader":y0(i)||y0(r)?"rowheader":"columnheader"},THEAD:()=>"rowgroup",TIME:()=>"time",TR:()=>"row",UL:()=>"list"};function m0(n){return!!n&&Xe(n)==="TH"}function y0(n){var e;return!n||Xe(n)!=="TD"?!1:!!((e=n.textContent)!=null&&e.trim()||n.children.length>0)}const ZT={DD:["DL","DIV"],DIV:["DL"],DT:["DL","DIV"],LI:["OL","UL"],TBODY:["TABLE"],TD:["TR"],TFOOT:["TABLE"],TH:["TR"],THEAD:["TABLE"],TR:["THEAD","TBODY","TFOOT","TABLE"]};function b0(n){var r;const e=((r=uh[Xe(n)])==null?void 0:r.call(uh,n))||"";if(!e)return null;let i=n;for(;i;){const l=bt(i),o=ZT[Xe(i)];if(!o||!l||!o.includes(Xe(l)))break;const u=cd(l);if((u==="none"||u==="presentation")&&!Zb(l,u))return u;i=l}return e}const WT=["alert","alertdialog","application","article","banner","blockquote","button","caption","cell","checkbox","code","columnheader","combobox","complementary","contentinfo","definition","deletion","dialog","directory","document","emphasis","feed","figure","form","generic","grid","gridcell","group","heading","img","insertion","link","list","listbox","listitem","log","main","mark","marquee","math","meter","menu","menubar","menuitem","menuitemcheckbox","menuitemradio","navigation","none","note","option","paragraph","presentation","progressbar","radio","radiogroup","region","row","rowgroup","rowheader","scrollbar","search","searchbox","separator","slider","spinbutton","status","strong","subscript","superscript","switch","tab","table","tablist","tabpanel","term","textbox","time","timer","toolbar","tooltip","tree","treegrid","treeitem"];function cd(n){return(n.getAttribute("role")||"").split(" ").map(i=>i.trim()).find(i=>WT.includes(i))||null}function Zb(n,e){return Jb(n,e)||JT(n)}function mt(n){const e=cd(n);if(!e)return b0(n);if(e==="none"||e==="presentation"){const i=b0(n);if(Zb(n,i))return i}return e}function Wb(n){return n===null?void 0:n.toLowerCase()==="true"}function ev(n){return["STYLE","SCRIPT","NOSCRIPT","TEMPLATE"].includes(Xe(n))}function ln(n){if(ev(n))return!0;const e=zi(n),i=n.nodeName==="SLOT";if((e==null?void 0:e.display)==="contents"&&!i){for(let l=n.firstChild;l;l=l.nextSibling)if(l.nodeType===1&&!ln(l)||l.nodeType===3&&Fb(l))return!1;return!0}return!(n.nodeName==="OPTION"&&!!n.closest("select"))&&!i&&!Xb(n,e)?!0:tv(n)}function tv(n){let e=Mi==null?void 0:Mi.get(n);if(e===void 0){if(e=!1,n.parentElement&&n.parentElement.shadowRoot&&!n.assignedSlot&&(e=!0),!e){const i=zi(n);e=!i||i.display==="none"||Wb(n.getAttribute("aria-hidden"))===!0}if(!e){const i=bt(n);i&&(e=tv(i))}Mi==null||Mi.set(n,e)}return e}function Nr(n,e){if(!e)return[];const i=Kb(n);if(!i)return[];try{const r=e.split(" ").filter(o=>!!o),l=[];for(const o of r){const u=i.querySelector("#"+CSS.escape(o));u&&!l.includes(u)&&l.push(u)}return l}catch{return[]}}function Pn(n){return n.trim()}function Fa(n){return n.split(" ").map(e=>e.replace(/\r\n/g,` -`).replace(/[\u200b\u00ad]/g,"").replace(/\s\s*/g," ")).join(" ").trim()}function v0(n,e){const i=[...n.querySelectorAll(e)];for(const r of Nr(n,n.getAttribute("aria-owns")))r.matches(e)&&i.push(r),i.push(...r.querySelectorAll(e));return i}function Qa(n,e){const i=e==="::before"?wd:e==="::after"?xd:Sd;if(i!=null&&i.has(n))return i==null?void 0:i.get(n);const r=zi(n,e);let l;if(r){const o=r.content;o&&o!=="none"&&o!=="normal"&&r.display!=="none"&&r.visibility!=="hidden"&&(l=eE(n,o,!!e))}return e&&l!==void 0&&((r==null?void 0:r.display)||"inline")!=="inline"&&(l=" "+l+" "),i&&i.set(n,l),l}function eE(n,e,i){if(!(!e||e==="none"||e==="normal"))try{let r=ab(e).filter(f=>!(f instanceof ic));const l=r.findIndex(f=>f instanceof dt&&f.value==="/");if(l!==-1)r=r.slice(l+1);else if(!i)return;const o=[];let u=0;for(;uyn(o,{includeHidden:e,visitedElements:new Set,embeddedInDescribedBy:{element:o,hidden:ln(o)}})).join(" "))}else n.hasAttribute("aria-description")?r=Fa(n.getAttribute("aria-description")||""):r=Fa(n.getAttribute("title")||"");i==null||i.set(n,r)}return r}function nE(n){const e=n.getAttribute("aria-invalid");return!e||e.trim()===""||e.toLocaleLowerCase()==="false"?"false":e==="true"||e==="grammar"||e==="spelling"?e:"true"}function iE(n){if("validity"in n){const e=n.validity;return(e==null?void 0:e.valid)===!1}return!1}function sE(n){const e=dr;let i=dr==null?void 0:dr.get(n);if(i===void 0){i="";const r=nE(n)!=="false",l=iE(n);if(r||l){const o=n.getAttribute("aria-errormessage");i=Nr(n,o).map(h=>Fa(yn(h,{visitedElements:new Set,embeddedInDescribedBy:{element:h,hidden:ln(h)}}))).join(" ").trim()}e==null||e.set(n,i)}return i}function yn(n,e){var h,g,y,m;if(e.visitedElements.has(n))return"";const i={...e,embeddedInTargetElement:e.embeddedInTargetElement==="self"?"descendant":e.embeddedInTargetElement};if(!e.includeHidden){const w=!!((h=e.embeddedInLabelledBy)!=null&&h.hidden)||!!((g=e.embeddedInDescribedBy)!=null&&g.hidden)||!!((y=e.embeddedInNativeTextAlternative)!=null&&y.hidden)||!!((m=e.embeddedInLabel)!=null&&m.hidden);if(ev(n)||!w&&ln(n))return e.visitedElements.add(n),""}const r=nv(n);if(!e.embeddedInLabelledBy){const w=(r||[]).map(v=>yn(v,{...e,embeddedInLabelledBy:{element:v,hidden:ln(v)},embeddedInDescribedBy:void 0,embeddedInTargetElement:void 0,embeddedInLabel:void 0,embeddedInNativeTextAlternative:void 0})).join(" ");if(w)return w}const l=mt(n)||"",o=Xe(n);if(e.embeddedInLabel||e.embeddedInLabelledBy||e.embeddedInTargetElement==="descendant"){const w=[...n.labels||[]].includes(n),v=(r||[]).includes(n);if(!w&&!v){if(l==="textbox")return e.visitedElements.add(n),o==="INPUT"||o==="TEXTAREA"?n.value:n.textContent||"";if(["combobox","listbox"].includes(l)){e.visitedElements.add(n);let E;if(o==="SELECT")E=[...n.selectedOptions],!E.length&&n.options.length&&E.push(n.options[0]);else{const x=l==="combobox"?v0(n,"*").find(_=>mt(_)==="listbox"):n;E=x?v0(x,'[aria-selected="true"]').filter(_=>mt(_)==="option"):[]}return!E.length&&o==="INPUT"?n.value:E.map(x=>yn(x,i)).join(" ")}if(["progressbar","scrollbar","slider","spinbutton","meter"].includes(l))return e.visitedElements.add(n),n.hasAttribute("aria-valuetext")?n.getAttribute("aria-valuetext")||"":n.hasAttribute("aria-valuenow")?n.getAttribute("aria-valuenow")||"":n.getAttribute("value")||"";if(["menu"].includes(l))return e.visitedElements.add(n),""}}const u=n.getAttribute("aria-label")||"";if(Pn(u))return e.visitedElements.add(n),u;if(!["presentation","none"].includes(l)){if(o==="INPUT"&&["button","submit","reset"].includes(n.type)){e.visitedElements.add(n);const w=n.value||"";return Pn(w)?w:n.type==="submit"?"Submit":n.type==="reset"?"Reset":n.getAttribute("title")||""}if(o==="INPUT"&&n.type==="file"){e.visitedElements.add(n);const w=n.labels||[];return w.length&&!e.embeddedInLabelledBy?Ra(w,e):"Choose File"}if(o==="INPUT"&&n.type==="image"){e.visitedElements.add(n);const w=n.labels||[];if(w.length&&!e.embeddedInLabelledBy)return Ra(w,e);const v=n.getAttribute("alt")||"";if(Pn(v))return v;const E=n.getAttribute("title")||"";return Pn(E)?E:"Submit"}if(!r&&o==="BUTTON"){e.visitedElements.add(n);const w=n.labels||[];if(w.length)return Ra(w,e)}if(!r&&o==="OUTPUT"){e.visitedElements.add(n);const w=n.labels||[];return w.length?Ra(w,e):n.getAttribute("title")||""}if(!r&&(o==="TEXTAREA"||o==="SELECT"||o==="INPUT")){e.visitedElements.add(n);const w=n.labels||[];if(w.length)return Ra(w,e);const v=o==="INPUT"&&["text","password","search","tel","email","url"].includes(n.type)||o==="TEXTAREA",E=n.getAttribute("placeholder")||"",x=n.getAttribute("title")||"";return!v||x?x:E}if(!r&&o==="FIELDSET"){e.visitedElements.add(n);for(let v=n.firstElementChild;v;v=v.nextElementSibling)if(Xe(v)==="LEGEND")return yn(v,{...i,embeddedInNativeTextAlternative:{element:v,hidden:ln(v)}});return n.getAttribute("title")||""}if(!r&&o==="FIGURE"){e.visitedElements.add(n);for(let v=n.firstElementChild;v;v=v.nextElementSibling)if(Xe(v)==="FIGCAPTION")return yn(v,{...i,embeddedInNativeTextAlternative:{element:v,hidden:ln(v)}});return n.getAttribute("title")||""}if(o==="IMG"){e.visitedElements.add(n);const w=n.getAttribute("alt")||"";return Pn(w)?w:n.getAttribute("title")||""}if(o==="TABLE"){e.visitedElements.add(n);for(let v=n.firstElementChild;v;v=v.nextElementSibling)if(Xe(v)==="CAPTION")return yn(v,{...i,embeddedInNativeTextAlternative:{element:v,hidden:ln(v)}});const w=n.getAttribute("summary")||"";if(w)return w}if(o==="AREA"){e.visitedElements.add(n);const w=n.getAttribute("alt")||"";return Pn(w)?w:n.getAttribute("title")||""}if(o==="SVG"||n.ownerSVGElement){e.visitedElements.add(n);for(let w=n.firstElementChild;w;w=w.nextElementSibling)if(Xe(w)==="TITLE"&&w.ownerSVGElement)return yn(w,{...i,embeddedInLabelledBy:{element:w,hidden:ln(w)}})}if(n.ownerSVGElement&&o==="A"){const w=n.getAttribute("xlink:title")||"";if(Pn(w))return e.visitedElements.add(n),w}}const f=o==="SUMMARY"&&!["presentation","none"].includes(l);if(tE(l,e.embeddedInTargetElement==="descendant")||f||e.embeddedInLabelledBy||e.embeddedInDescribedBy||e.embeddedInLabel||e.embeddedInNativeTextAlternative){e.visitedElements.add(n);const w=rE(n,i);if(e.embeddedInTargetElement==="self"?Pn(w):w)return w}if(!["presentation","none"].includes(l)||o==="IFRAME"){e.visitedElements.add(n);const w=n.getAttribute("title")||"";if(Pn(w))return w}return e.visitedElements.add(n),""}function rE(n,e){const i=[],r=(o,u)=>{var f;if(!(u&&o.assignedSlot))if(o.nodeType===1){const h=((f=zi(o))==null?void 0:f.display)||"inline";let g=yn(o,e);(h!=="inline"||o.nodeName==="BR")&&(g=" "+g+" "),i.push(g)}else o.nodeType===3&&i.push(o.textContent||"")};i.push(Qa(n,"::before")||"");const l=Qa(n);if(l!==void 0)i.push(l);else{const o=n.nodeName==="SLOT"?n.assignedNodes():[];if(o.length)for(const u of o)r(u,!1);else{for(let u=n.firstChild;u;u=u.nextSibling)r(u,!0);if(n.shadowRoot)for(let u=n.shadowRoot.firstChild;u;u=u.nextSibling)r(u,!0);for(const u of Nr(n,n.getAttribute("aria-owns")))r(u,!0)}}return i.push(Qa(n,"::after")||""),i.join("")}const ud=["gridcell","option","row","tab","rowheader","columnheader","treeitem"];function iv(n){return Xe(n)==="OPTION"?n.selected:ud.includes(mt(n)||"")?Wb(n.getAttribute("aria-selected"))===!0:!1}const fd=["checkbox","menuitemcheckbox","option","radio","switch","menuitemradio","treeitem"];function sv(n){const e=hd(n,!0);return e==="error"?!1:e}function aE(n){return hd(n,!0)}function lE(n){return hd(n,!1)}function hd(n,e){const i=Xe(n);if(e&&i==="INPUT"&&n.indeterminate)return"mixed";if(i==="INPUT"&&["checkbox","radio"].includes(n.type))return n.checked;if(fd.includes(mt(n)||"")){const r=n.getAttribute("aria-checked");return r==="true"?!0:e&&r==="mixed"?"mixed":!1}return"error"}const oE=["checkbox","combobox","grid","gridcell","listbox","radiogroup","slider","spinbutton","textbox","columnheader","rowheader","searchbox","switch","treegrid"];function cE(n){const e=Xe(n);return["INPUT","TEXTAREA","SELECT"].includes(e)?n.hasAttribute("readonly"):oE.includes(mt(n)||"")?n.getAttribute("aria-readonly")==="true":n.isContentEditable?!1:"error"}const dd=["button"];function rv(n){if(dd.includes(mt(n)||"")){const e=n.getAttribute("aria-pressed");if(e==="true")return!0;if(e==="mixed")return"mixed"}return!1}const pd=["application","button","checkbox","combobox","gridcell","link","listbox","menuitem","row","rowheader","tab","treeitem","columnheader","menuitemcheckbox","menuitemradio","rowheader","switch"];function av(n){if(Xe(n)==="DETAILS")return n.open;if(pd.includes(mt(n)||"")){const e=n.getAttribute("aria-expanded");return e===null?void 0:e==="true"}}const gd=["heading","listitem","row","treeitem"];function lv(n){const e={H1:1,H2:2,H3:3,H4:4,H5:5,H6:6}[Xe(n)];if(e)return e;if(gd.includes(mt(n)||"")){const i=n.getAttribute("aria-level"),r=i===null?Number.NaN:Number(i);if(Number.isInteger(r)&&r>=1)return r}return 0}const ov=["application","button","composite","gridcell","group","input","link","menuitem","scrollbar","separator","tab","checkbox","columnheader","combobox","grid","listbox","menu","menubar","menuitemcheckbox","menuitemradio","option","radio","radiogroup","row","rowheader","searchbox","select","slider","spinbutton","switch","tablist","textbox","toolbar","tree","treegrid","treeitem"];function uc(n){return cv(n)||uv(n)}function cv(n){return["BUTTON","INPUT","SELECT","TEXTAREA","OPTION","OPTGROUP"].includes(Xe(n))&&(n.hasAttribute("disabled")||uE(n)||fE(n))}function uE(n){return Xe(n)==="OPTION"&&!!n.closest("OPTGROUP[DISABLED]")}function fE(n){const e=n==null?void 0:n.closest("FIELDSET[DISABLED]");if(!e)return!1;const i=e.querySelector(":scope > LEGEND");return!i||!i.contains(n)}function uv(n,e=!1){if(!n)return!1;if(e||ov.includes(mt(n)||"")){const i=(n.getAttribute("aria-disabled")||"").toLowerCase();return i==="true"?!0:i==="false"?!1:uv(bt(n),!0)}return!1}function Ra(n,e){return[...n].map(i=>yn(i,{...e,embeddedInLabel:{element:i,hidden:ln(i)},embeddedInNativeTextAlternative:void 0,embeddedInLabelledBy:void 0,embeddedInDescribedBy:void 0,embeddedInTargetElement:void 0})).filter(i=>!!i).join(" ")}function hE(n){const e=_d;let i=n,r;const l=[];for(;i;i=bt(i)){const o=e.get(i);if(o!==void 0){r=o;break}l.push(i);const u=zi(i);if(!u){r=!0;break}const f=u.pointerEvents;if(f){r=f!=="none";break}}r===void 0&&(r=!0);for(const o of l)e.set(o,r);return r}let md,yd,bd,vd,dr,Mi,Sd,wd,xd,_d,fv=0;function vc(){ld(),++fv,md??(md=new Map),yd??(yd=new Map),bd??(bd=new Map),vd??(vd=new Map),dr??(dr=new Map),Mi??(Mi=new Map),Sd??(Sd=new Map),wd??(wd=new Map),xd??(xd=new Map),_d??(_d=new Map)}function Sc(){--fv||(md=void 0,yd=void 0,bd=void 0,vd=void 0,dr=void 0,Mi=void 0,Sd=void 0,wd=void 0,xd=void 0,_d=void 0),od()}const dE={button:"button",checkbox:"checkbox",image:"button",number:"spinbutton",radio:"radio",range:"slider",reset:"button",submit:"button"};let pE=0;function hv(n){return n.mode==="ai"?{visibility:"ariaOrVisible",refs:"interactable",refPrefix:n.refPrefix,includeGenericRole:!0,renderActive:!n.doNotRenderActive,renderCursorPointer:!0}:n.mode==="autoexpect"?{visibility:"ariaAndVisible",refs:"none"}:n.mode==="codegen"?{visibility:"aria",refs:"none",renderStringsAsRegex:!0}:{visibility:"aria",refs:"none"}}function Ja(n,e){const i=hv(e),r=new Set,l={root:{role:"fragment",name:"",children:[],props:{},box:cc(n),receivesPointerEvents:!0},elements:new Map,refs:new Map,iframeRefs:[]};Rh(l.root,n);const o=(f,h,g)=>{if(r.has(h))return;if(r.add(h),h.nodeType===Node.TEXT_NODE&&h.nodeValue){if(!g)return;const x=h.nodeValue;f.role!=="textbox"&&x&&f.children.push(h.nodeValue||"");return}if(h.nodeType!==Node.ELEMENT_NODE)return;const y=h,m=!ln(y);let w=m;if(i.visibility==="ariaOrVisible"&&(w=m||ji(y)),i.visibility==="ariaAndVisible"&&(w=m&&ji(y)),i.visibility==="aria"&&!w)return;const v=[];if(y.hasAttribute("aria-owns")){const x=y.getAttribute("aria-owns").split(/\s+/);for(const _ of x){const N=n.ownerDocument.getElementById(_);N&&v.push(N)}}const E=w?gE(y,i):null;E&&(E.ref&&(l.elements.set(E.ref,y),l.refs.set(y,E.ref),E.role==="iframe"&&l.iframeRefs.push(E.ref)),f.children.push(E)),u(E||f,y,v,w)};function u(f,h,g,y){var E;const w=(((E=zi(h))==null?void 0:E.display)||"inline")!=="inline"||h.nodeName==="BR"?" ":"";w&&f.children.push(w),f.children.push(Qa(h,"::before")||"");const v=h.nodeName==="SLOT"?h.assignedNodes():[];if(v.length)for(const x of v)o(f,x,y);else{for(let x=h.firstChild;x;x=x.nextSibling)x.assignedSlot||o(f,x,y);if(h.shadowRoot)for(let x=h.shadowRoot.firstChild;x;x=x.nextSibling)o(f,x,y)}for(const x of g)o(f,x,y);if(f.children.push(Qa(h,"::after")||""),w&&f.children.push(w),f.children.length===1&&f.name===f.children[0]&&(f.children=[]),f.role==="link"&&h.hasAttribute("href")){const x=h.getAttribute("href");f.props.url=x}if(f.role==="textbox"&&h.hasAttribute("placeholder")&&h.getAttribute("placeholder")!==f.name){const x=h.getAttribute("placeholder");f.props.placeholder=x}}vc();try{o(l.root,n,!0)}finally{Sc()}return yE(l.root),mE(l.root),l}function w0(n,e){if(e.refs==="none"||e.refs==="interactable"&&(!n.box.visible||!n.receivesPointerEvents))return;const i=Ed(n);let r=i._ariaRef;(!r||r.role!==n.role||r.name!==n.name)&&(r={role:n.role,name:n.name,ref:(e.refPrefix??"")+"e"+ ++pE},i._ariaRef=r),n.ref=r.ref}function gE(n,e){const i=n.ownerDocument.activeElement===n;if(n.nodeName==="IFRAME"){const g={role:"iframe",name:"",children:[],props:{},box:cc(n),receivesPointerEvents:!0,active:i};return Rh(g,n),w0(g,e),g}const r=e.includeGenericRole?"generic":null,l=mt(n)??r;if(!l||l==="presentation"||l==="none")return null;const o=At(sl(n,!1)||""),u=hE(n),f=cc(n);if(l==="generic"&&f.inline&&n.childNodes.length===1&&n.childNodes[0].nodeType===Node.TEXT_NODE)return null;const h={role:l,name:o,children:[],props:{},box:f,receivesPointerEvents:u,active:i};return Rh(h,n),w0(h,e),fd.includes(l)&&(h.checked=sv(n)),ov.includes(l)&&(h.disabled=uc(n)),pd.includes(l)&&(h.expanded=av(n)),gd.includes(l)&&(h.level=lv(n)),dd.includes(l)&&(h.pressed=rv(n)),ud.includes(l)&&(h.selected=iv(n)),(n instanceof HTMLInputElement||n instanceof HTMLTextAreaElement)&&n.type!=="checkbox"&&n.type!=="radio"&&n.type!=="file"&&(h.children=[n.value]),h}function mE(n){const e=i=>{const r=[];for(const o of i.children||[]){if(typeof o=="string"){r.push(o);continue}const u=e(o);r.push(...u)}return i.role==="generic"&&!i.name&&r.length<=1&&r.every(o=>typeof o!="string"&&!!o.ref)?r:(i.children=r,[i])};e(n)}function yE(n){const e=(r,l)=>{if(!r.length)return;const o=At(r.join(""));o&&l.push(o),r.length=0},i=r=>{const l=[],o=[];for(const u of r.children||[])typeof u=="string"?o.push(u):(e(o,l),i(u),l.push(u));e(o,l),r.children=l.length?l:[],r.children.length===1&&r.children[0]===r.name&&(r.children=[])};i(n)}function bE(n,e){return e?n?typeof e=="string"?n===e:!!n.match(new RegExp(e.pattern)):!1:!0}function x0(n,e){if(!(e!=null&&e.normalized))return!0;if(!n)return!1;if(n===e.normalized||n===e.raw)return!0;const i=vE(e);return i?!!n.match(i):!1}const fh=Symbol("cachedRegex");function vE(n){if(n[fh]!==void 0)return n[fh];const{raw:e}=n,i=e.startsWith("/")&&e.endsWith("/")&&e.length>1;let r;try{r=i?new RegExp(e.slice(1,-1)):null}catch{r=null}return n[fh]=r,r}function SE(n,e){const i=Ja(n,{mode:"expect"});return{matches:dv(i.root,e,!1,!1),received:{raw:Pa(i,{mode:"expect"}),regex:Pa(i,{mode:"codegen"})}}}function wE(n,e){const i=Ja(n,{mode:"expect"}).root;return dv(i,e,!0,!1).map(l=>Ed(l))}function Td(n,e,i){var r;return typeof n=="string"&&e.kind==="text"?x0(n,e.text):n===null||typeof n!="object"||e.kind!=="role"||e.role!=="fragment"&&e.role!==n.role||e.checked!==void 0&&e.checked!==n.checked||e.disabled!==void 0&&e.disabled!==n.disabled||e.expanded!==void 0&&e.expanded!==n.expanded||e.level!==void 0&&e.level!==n.level||e.pressed!==void 0&&e.pressed!==n.pressed||e.selected!==void 0&&e.selected!==n.selected||!bE(n.name,e.name)||!x0(n.props.url,(r=e.props)==null?void 0:r.url)?!1:e.containerMode==="contain"?T0(n.children||[],e.children||[]):e.containerMode==="equal"?_0(n.children||[],e.children||[],!1):e.containerMode==="deep-equal"||i?_0(n.children||[],e.children||[],!0):T0(n.children||[],e.children||[])}function _0(n,e,i){if(e.length!==n.length)return!1;for(let r=0;rn.length)return!1;const i=n.slice(),r=e.slice();for(const l of r){let o=i.shift();for(;o&&!Td(o,l,!1);)o=i.shift();if(!o)return!1}return!0}function dv(n,e,i,r){const l=[],o=(u,f)=>{if(Td(u,e,r)){const h=typeof u=="string"?f:u;return h&&l.push(h),!i}if(typeof u=="string")return!1;for(const h of u.children||[])if(o(h,u))return!0;return!1};return o(n,null),l}function pv(n,e=new Map){n!=null&&n.ref&&e.set(n.ref,n);for(const i of(n==null?void 0:n.children)||[])typeof i!="string"&&pv(i,e);return e}function xE(n,e){var o;const i=pv(e==null?void 0:e.root),r=new Map,l=(u,f)=>{let h=u.children.length===(f==null?void 0:f.children.length)&&VT(u,f),g=h;for(let y=0;y{const o=e.get(l);if(o!=="same")if(o==="skip")for(const u of l.children)typeof u!="string"&&r(u);else i.push(l)};for(const l of n)typeof l=="string"?i.push(l):r(l);return i}function Pa(n,e,i){const r=hv(e),l=[],o=r.renderStringsAsRegex?EE:()=>!0,u=r.renderStringsAsRegex?TE:v=>v;let f=n.root.role==="fragment"?n.root.children:[n.root];const h=xE(n,i);i&&(f=_E(f,h));const g=(v,E)=>{const x=ch(u(v));x&&l.push(E+"- text: "+x)},y=(v,E)=>{let x=v.role;if(v.name&&v.name.length<=900){const _=u(v.name);if(_){const N=_.startsWith("/")&&_.endsWith("/")?_:JSON.stringify(_);x+=" "+N}}return v.checked==="mixed"&&(x+=" [checked=mixed]"),v.checked===!0&&(x+=" [checked]"),v.disabled&&(x+=" [disabled]"),v.expanded&&(x+=" [expanded]"),v.active&&r.renderActive&&(x+=" [active]"),v.level&&(x+=` [level=${v.level}]`),v.pressed==="mixed"&&(x+=" [pressed=mixed]"),v.pressed===!0&&(x+=" [pressed]"),v.selected===!0&&(x+=" [selected]"),v.ref&&(x+=` [ref=${v.ref}]`,E&&lc(v)&&(x+=" [cursor=pointer]")),x},m=v=>(v==null?void 0:v.children.length)===1&&typeof v.children[0]=="string"&&!Object.keys(v.props).length?v.children[0]:void 0,w=(v,E,x)=>{if(h.get(v)==="same"&&v.ref){l.push(E+`- ref=${v.ref} [unchanged]`);return}const _=!!i&&!E,N=E+"- "+(_?" ":"")+XT(y(v,x)),C=m(v);if(!v.children.length&&!Object.keys(v.props).length)l.push(N);else if(C!==void 0)o(v,C)?l.push(N+": "+ch(u(C))):l.push(N);else{l.push(N+":");for(const[D,K]of Object.entries(v.props))l.push(E+" - /"+D+": "+ch(K));const $=E+" ",I=!!v.ref&&x&&lc(v);for(const D of v.children)typeof D=="string"?g(o(v,D)?D:"",$):w(D,$,x&&!I)}};for(const v of f)typeof v=="string"?g(v,""):w(v,"",!!r.renderCursorPointer);return l.join(` -`)}function TE(n){const e=[{regex:/\b[\d,.]+[bkmBKM]+\b/,replacement:"[\\d,.]+[bkmBKM]+"},{regex:/\b\d+[hmsp]+\b/,replacement:"\\d+[hmsp]+"},{regex:/\b[\d,.]+[hmsp]+\b/,replacement:"[\\d,.]+[hmsp]+"},{regex:/\b\d+,\d+\b/,replacement:"\\d+,\\d+"},{regex:/\b\d+\.\d{2,}\b/,replacement:"\\d+\\.\\d+"},{regex:/\b\d{2,}\.\d+\b/,replacement:"\\d+\\.\\d+"},{regex:/\b\d{2,}\b/,replacement:"\\d+"}];let i="",r=0;const l=new RegExp(e.map(o=>"("+o.regex.source+")").join("|"),"g");return n.replace(l,(o,...u)=>{const f=u[u.length-2],h=u.slice(0,-2);i+=rc(n.slice(r,f));for(let g=0;ge.length)return!1;const i=e.length<=200&&n.name.length<=200?l_(e,n.name):"";let r=e;for(;i&&r.includes(i);)r=r.replace(i,"");return r.trim().length/e.length>.1}const gv=Symbol("element");function Ed(n){return n[gv]}function Rh(n,e){n[gv]=e}function AE(n,e){const i=YT(n,e);return i?Ed(i):void 0}const E0=":host{font-size:13px;font-family:system-ui,Ubuntu,Droid Sans,sans-serif;color:#333}svg{position:absolute;height:0}x-pw-tooltip{-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);background-color:#fff;border-radius:6px;box-shadow:0 .5rem 1.2rem #0000004d;display:none;font-size:12.8px;font-weight:400;left:0;line-height:1.5;max-width:600px;position:absolute;top:0;padding:0;flex-direction:column;overflow:hidden}x-pw-tooltip-line{display:flex;max-width:600px;padding:6px;-webkit-user-select:none;user-select:none;cursor:pointer}x-pw-tooltip-footer{display:flex;max-width:600px;padding:6px;-webkit-user-select:none;user-select:none;color:#777}x-pw-dialog{background-color:#fff;pointer-events:auto;border-radius:6px;box-shadow:0 .5rem 1.2rem #0000004d;display:flex;flex-direction:column;position:absolute;z-index:10;font-size:13px}x-pw-dialog:not(.autosize){width:400px;height:150px}x-pw-dialog-body{display:flex;flex-direction:column;flex:auto}x-pw-dialog-body label{margin:5px 8px;display:flex;flex-direction:row;align-items:center}x-pw-highlight{position:absolute;top:0;left:0;width:0;height:0}x-pw-action-point{position:absolute;width:20px;height:20px;background:red;border-radius:10px;margin:-10px 0 0 -10px;z-index:2}x-pw-separator{height:1px;margin:6px 9px;background:#949494e5}x-pw-tool-gripper{height:28px;width:24px;margin:2px 0;cursor:grab}x-pw-tool-gripper:active{cursor:grabbing}x-pw-tool-gripper>x-div{width:16px;height:16px;margin:6px 4px;clip-path:url(#icon-gripper);background-color:#555}x-pw-tools-list>label{display:flex;align-items:center;margin:0 10px;-webkit-user-select:none;user-select:none}x-pw-tools-list{display:flex;width:100%;border-bottom:1px solid #dddddd}x-pw-tool-item{pointer-events:auto;height:28px;width:28px;border-radius:3px}x-pw-tool-item:not(.disabled){cursor:pointer}x-pw-tool-item:not(.disabled):hover{background-color:#dbdbdb}x-pw-tool-item.toggled{background-color:#8acae480}x-pw-tool-item.toggled:not(.disabled):hover{background-color:#8acae4c4}x-pw-tool-item>x-div{width:16px;height:16px;margin:6px;background-color:#3a3a3a}x-pw-tool-item.disabled>x-div{background-color:#61616180;cursor:default}x-pw-tool-item.record.toggled{background-color:transparent}x-pw-tool-item.record.toggled:not(.disabled):hover{background-color:#dbdbdb}x-pw-tool-item.record.toggled>x-div{background-color:#a1260d}x-pw-tool-item.record.disabled.toggled>x-div{opacity:.8}x-pw-tool-item.accept>x-div{background-color:#388a34}x-pw-tool-item.record>x-div{clip-path:url(#icon-circle-large-filled)}x-pw-tool-item.record.toggled>x-div{clip-path:url(#icon-stop-circle)}x-pw-tool-item.pick-locator>x-div{clip-path:url(#icon-inspect)}x-pw-tool-item.text>x-div{clip-path:url(#icon-whole-word)}x-pw-tool-item.visibility>x-div{clip-path:url(#icon-eye)}x-pw-tool-item.value>x-div{clip-path:url(#icon-symbol-constant)}x-pw-tool-item.snapshot>x-div{clip-path:url(#icon-gist)}x-pw-tool-item.accept>x-div{clip-path:url(#icon-check)}x-pw-tool-item.cancel>x-div{clip-path:url(#icon-close)}x-pw-tool-item.succeeded>x-div{clip-path:url(#icon-pass);background-color:#388a34!important}x-pw-overlay{position:absolute;top:0;max-width:min-content;z-index:2147483647;background:transparent;pointer-events:auto}x-pw-overlay x-pw-tools-list{background-color:#fffd;box-shadow:#0000001a 0 5px 5px;border-radius:3px;border-bottom:none}x-pw-overlay x-pw-tool-item{margin:2px}textarea.text-editor{font-family:system-ui,Ubuntu,Droid Sans,sans-serif;flex:auto;border:none;margin:6px 10px;color:#333;outline:1px solid transparent!important;resize:none;padding:0;font-size:13px}textarea.text-editor.does-not-match{outline:1px solid red!important}x-div{display:block}x-spacer{flex:auto}*{box-sizing:border-box}*[hidden]{display:none!important}x-locator-editor{flex:none;width:100%;height:60px;padding:4px;border-bottom:1px solid #dddddd;outline:1px solid transparent}x-locator-editor.does-not-match{outline:1px solid red}.CodeMirror{width:100%!important;height:100%!important}x-pw-action-list{flex:auto;display:flex;flex-direction:column;-webkit-user-select:none;user-select:none}x-pw-action-item{padding:6px 10px;cursor:pointer;overflow:hidden}x-pw-action-item:hover{background-color:#f2f2f2}x-pw-action-item:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}";class hh{constructor(e){this._renderedEntries=[],this._language="javascript",this._injectedScript=e;const i=e.document;if(this._isUnderTest=e.isUnderTest,this._glassPaneElement=i.createElement("x-pw-glass"),this._glassPaneElement.style.position="fixed",this._glassPaneElement.style.top="0",this._glassPaneElement.style.right="0",this._glassPaneElement.style.bottom="0",this._glassPaneElement.style.left="0",this._glassPaneElement.style.zIndex="2147483647",this._glassPaneElement.style.pointerEvents="none",this._glassPaneElement.style.display="flex",this._glassPaneElement.style.backgroundColor="transparent",this._actionPointElement=i.createElement("x-pw-action-point"),this._actionPointElement.setAttribute("hidden","true"),this._glassPaneShadow=this._glassPaneElement.attachShadow({mode:this._isUnderTest?"open":"closed"}),typeof this._glassPaneShadow.adoptedStyleSheets.push=="function"){const r=new this._injectedScript.window.CSSStyleSheet;r.replaceSync(E0),this._glassPaneShadow.adoptedStyleSheets.push(r)}else{const r=this._injectedScript.document.createElement("style");r.textContent=E0,this._glassPaneShadow.appendChild(r)}this._glassPaneShadow.appendChild(this._actionPointElement)}install(){this._injectedScript.document.documentElement&&(!this._injectedScript.document.documentElement.contains(this._glassPaneElement)||this._glassPaneElement.nextElementSibling)&&this._injectedScript.document.documentElement.appendChild(this._glassPaneElement)}setLanguage(e){this._language=e}runHighlightOnRaf(e){this._rafRequest&&this._injectedScript.utils.builtins.cancelAnimationFrame(this._rafRequest);const i=this._injectedScript.querySelectorAll(e,this._injectedScript.document.documentElement),r=Oi(this._language,An(e)),l=i.length>1?"#f6b26b7f":"#6fa8dc7f";this.updateHighlight(i.map((o,u)=>{const f=i.length>1?` [${u+1} of ${i.length}]`:"";return{element:o,color:l,tooltipText:r+f}})),this._rafRequest=this._injectedScript.utils.builtins.requestAnimationFrame(()=>this.runHighlightOnRaf(e))}uninstall(){this._rafRequest&&this._injectedScript.utils.builtins.cancelAnimationFrame(this._rafRequest),this._glassPaneElement.remove()}showActionPoint(e,i){this._actionPointElement.style.top=i+"px",this._actionPointElement.style.left=e+"px",this._actionPointElement.hidden=!1}hideActionPoint(){this._actionPointElement.hidden=!0}clearHighlight(){var e,i;for(const r of this._renderedEntries)(e=r.highlightElement)==null||e.remove(),(i=r.tooltipElement)==null||i.remove();this._renderedEntries=[]}maskElements(e,i){this.updateHighlight(e.map(r=>({element:r,color:i})))}updateHighlight(e){if(!this._highlightIsUpToDate(e)){this.clearHighlight();for(const i of e){const r=this._createHighlightElement();this._glassPaneShadow.appendChild(r);let l;if(i.tooltipText){l=this._injectedScript.document.createElement("x-pw-tooltip"),this._glassPaneShadow.appendChild(l),l.style.top="0",l.style.left="0",l.style.display="flex";const o=this._injectedScript.document.createElement("x-pw-tooltip-line");o.textContent=i.tooltipText,l.appendChild(o)}this._renderedEntries.push({targetElement:i.element,color:i.color,tooltipElement:l,highlightElement:r})}for(const i of this._renderedEntries){if(i.box=i.targetElement.getBoundingClientRect(),!i.tooltipElement)continue;const{anchorLeft:r,anchorTop:l}=this.tooltipPosition(i.box,i.tooltipElement);i.tooltipTop=l,i.tooltipLeft=r}for(const i of this._renderedEntries){i.tooltipElement&&(i.tooltipElement.style.top=i.tooltipTop+"px",i.tooltipElement.style.left=i.tooltipLeft+"px");const r=i.box;i.highlightElement.style.backgroundColor=i.color,i.highlightElement.style.left=r.x+"px",i.highlightElement.style.top=r.y+"px",i.highlightElement.style.width=r.width+"px",i.highlightElement.style.height=r.height+"px",i.highlightElement.style.display="block",this._isUnderTest&&console.error("Highlight box for test: "+JSON.stringify({x:r.x,y:r.y,width:r.width,height:r.height}))}}}firstBox(){var e;return(e=this._renderedEntries[0])==null?void 0:e.box}firstTooltipBox(){const e=this._renderedEntries[0];if(!(!e||!e.tooltipElement||e.tooltipLeft===void 0||e.tooltipTop===void 0))return{x:e.tooltipLeft,y:e.tooltipTop,left:e.tooltipLeft,top:e.tooltipTop,width:e.tooltipElement.offsetWidth,height:e.tooltipElement.offsetHeight,bottom:e.tooltipTop+e.tooltipElement.offsetHeight,right:e.tooltipLeft+e.tooltipElement.offsetWidth,toJSON:()=>{}}}tooltipPosition(e,i){const r=i.offsetWidth,l=i.offsetHeight,o=this._glassPaneElement.offsetWidth,u=this._glassPaneElement.offsetHeight;let f=Math.max(5,e.left);f+r>o-5&&(f=o-r-5);let h=Math.max(0,e.bottom)+5;return h+l>u-5&&(Math.max(0,e.top)>l+5?h=Math.max(0,e.top)-l-5:h=u-5-l),{anchorLeft:f,anchorTop:h}}_highlightIsUpToDate(e){if(e.length!==this._renderedEntries.length)return!1;for(let i=0;ii))return r+Math.max(e.bottom-n.bottom,0)+Math.max(n.top-e.top,0)}function CE(n,e,i){const r=e.left-n.right;if(!(r<0||i!==void 0&&r>i))return r+Math.max(e.bottom-n.bottom,0)+Math.max(n.top-e.top,0)}function kE(n,e,i){const r=e.top-n.bottom;if(!(r<0||i!==void 0&&r>i))return r+Math.max(n.left-e.left,0)+Math.max(e.right-n.right,0)}function ME(n,e,i){const r=n.top-e.bottom;if(!(r<0||i!==void 0&&r>i))return r+Math.max(n.left-e.left,0)+Math.max(e.right-n.right,0)}function OE(n,e,i){const r=i===void 0?50:i;let l=0;return n.left-e.right>=0&&(l+=n.left-e.right),e.left-n.right>=0&&(l+=e.left-n.right),e.top-n.bottom>=0&&(l+=e.top-n.bottom),n.top-e.bottom>=0&&(l+=n.top-e.bottom),l>r?void 0:l}const jE=["left-of","right-of","above","below","near"];function mv(n,e,i,r){const l=e.getBoundingClientRect(),o={"left-of":CE,"right-of":NE,above:kE,below:ME,near:OE}[n];let u;for(const f of i){if(f===e)continue;const h=o(l,f.getBoundingClientRect(),r);h!==void 0&&(u===void 0||h"?!!i:e.op==="="?r instanceof RegExp?typeof i=="string"&&!!i.match(r):i===r:typeof i!="string"||typeof r!="string"?!1:e.op==="*="?i.includes(r):e.op==="^="?i.startsWith(r):e.op==="$="?i.endsWith(r):e.op==="|="?i===r||i.startsWith(r+"-"):e.op==="~="?i.split(" ").includes(r):!1}function Ad(n){const e=n.ownerDocument;return n.nodeName==="SCRIPT"||n.nodeName==="NOSCRIPT"||n.nodeName==="STYLE"||e.head&&e.head.contains(n)}function Ut(n,e){let i=n.get(e);if(i===void 0){if(i={full:"",normalized:"",immediate:[]},!Ad(e)){let r="";if(e instanceof HTMLInputElement&&(e.type==="submit"||e.type==="button"))i={full:e.value,normalized:At(e.value),immediate:[e.value]};else{for(let l=e.firstChild;l;l=l.nextSibling)if(l.nodeType===Node.TEXT_NODE)i.full+=l.nodeValue||"",r+=l.nodeValue||"";else{if(l.nodeType===Node.COMMENT_NODE)continue;r&&i.immediate.push(r),r="",l.nodeType===Node.ELEMENT_NODE&&(i.full+=Ut(n,l).full)}r&&i.immediate.push(r),e.shadowRoot&&(i.full+=Ut(n,e.shadowRoot).full),i.full&&(i.normalized=At(i.full))}}n.set(e,i)}return i}function wc(n,e,i){if(Ad(e)||!i(Ut(n,e)))return"none";for(let r=e.firstChild;r;r=r.nextSibling)if(r.nodeType===Node.ELEMENT_NODE&&i(Ut(n,r)))return"selfAndChildren";return e.shadowRoot&&i(Ut(n,e.shadowRoot))?"selfAndChildren":"self"}function vv(n,e){const i=nv(e);if(i)return i.map(o=>Ut(n,o));const r=e.getAttribute("aria-label");if(r!==null&&r.trim())return[{full:r,normalized:At(r),immediate:[r]}];const l=e.nodeName==="INPUT"&&e.type!=="hidden";if(["BUTTON","METER","OUTPUT","PROGRESS","SELECT","TEXTAREA"].includes(e.nodeName)||l){const o=e.labels;if(o)return[...o].map(u=>Ut(n,u))}return[]}function A0(n){return n.displayName||n.name||"Anonymous"}function LE(n){if(n.type)switch(typeof n.type){case"function":return A0(n.type);case"string":return n.type;case"object":return n.type.displayName||(n.type.render?A0(n.type.render):"")}if(n._currentElement){const e=n._currentElement.type;if(typeof e=="string")return e;if(typeof e=="function")return e.displayName||e.name||"Anonymous"}return""}function RE(n){var e;return n.key??((e=n._currentElement)==null?void 0:e.key)}function DE(n){if(n.child){const i=[];for(let r=n.child;r;r=r.sibling)i.push(r);return i}if(!n._currentElement)return[];const e=i=>{var l;const r=(l=i._currentElement)==null?void 0:l.type;return typeof r=="function"||typeof r=="string"};if(n._renderedComponent){const i=n._renderedComponent;return e(i)?[i]:[]}return n._renderedChildren?[...Object.values(n._renderedChildren)].filter(e):[]}function zE(n){var r;const e=n.memoizedProps||((r=n._currentElement)==null?void 0:r.props);if(!e||typeof e=="string")return e;const i={...e};return delete i.children,i}function Sv(n){var r;const e={key:RE(n),name:LE(n),children:DE(n).map(Sv),rootElements:[],props:zE(n)},i=n.stateNode||n._hostNode||((r=n._renderedComponent)==null?void 0:r._hostNode);if(i instanceof Element)e.rootElements.push(i);else for(const l of e.children)e.rootElements.push(...l.rootElements);return e}function wv(n,e,i=[]){e(n)&&i.push(n);for(const r of n.children)wv(r,e,i);return i}function xv(n,e=[]){const r=(n.ownerDocument||n).createTreeWalker(n,NodeFilter.SHOW_ELEMENT);do{const l=r.currentNode,o=l,u=Object.keys(o).find(h=>h.startsWith("__reactContainer")&&o[h]!==null);if(u)e.push(o[u].stateNode.current);else{const h="_reactRootContainer";o.hasOwnProperty(h)&&o[h]!==null&&e.push(o[h]._internalRoot.current)}if(l instanceof Element&&l.hasAttribute("data-reactroot"))for(const h of Object.keys(l))(h.startsWith("__reactInternalInstance")||h.startsWith("__reactFiber"))&&e.push(l[h]);const f=l instanceof Element?l.shadowRoot:null;f&&xv(f,e)}while(r.nextNode());return e}const BE=()=>({queryAll(n,e){const{name:i,attributes:r}=ds(e,!1),u=xv(n.ownerDocument||n).map(h=>Sv(h)).map(h=>wv(h,g=>{const y=g.props??{};if(g.key!==void 0&&(y.key=g.key),i&&g.name!==i||g.rootElements.some(m=>!il(n,m)))return!1;for(const m of r)if(!yv(y,m))return!1;return!0})).flat(),f=new Set;for(const h of u)for(const g of h.rootElements)f.add(g);return[...f]}}),_v=["selected","checked","pressed","expanded","level","disabled","name","include-hidden"];_v.sort();function Da(n,e,i){if(!e.includes(i))throw new Error(`"${n}" attribute is only supported for roles: ${e.slice().sort().map(r=>`"${r}"`).join(", ")}`)}function rr(n,e){if(n.op!==""&&!e.includes(n.value))throw new Error(`"${n.name}" must be one of ${e.map(i=>JSON.stringify(i)).join(", ")}`)}function ar(n,e){if(!e.includes(n.op))throw new Error(`"${n.name}" does not support "${n.op}" matcher`)}function UE(n,e){const i={role:e};for(const r of n)switch(r.name){case"checked":{Da(r.name,fd,e),rr(r,[!0,!1,"mixed"]),ar(r,["","="]),i.checked=r.op===""?!0:r.value;break}case"pressed":{Da(r.name,dd,e),rr(r,[!0,!1,"mixed"]),ar(r,["","="]),i.pressed=r.op===""?!0:r.value;break}case"selected":{Da(r.name,ud,e),rr(r,[!0,!1]),ar(r,["","="]),i.selected=r.op===""?!0:r.value;break}case"expanded":{Da(r.name,pd,e),rr(r,[!0,!1]),ar(r,["","="]),i.expanded=r.op===""?!0:r.value;break}case"level":{if(Da(r.name,gd,e),typeof r.value=="string"&&(r.value=+r.value),r.op!=="="||typeof r.value!="number"||Number.isNaN(r.value))throw new Error('"level" attribute must be compared to a number');i.level=r.value;break}case"disabled":{rr(r,[!0,!1]),ar(r,["","="]),i.disabled=r.op===""?!0:r.value;break}case"name":{if(r.op==="")throw new Error('"name" attribute must have a value');if(typeof r.value!="string"&&!(r.value instanceof RegExp))throw new Error('"name" attribute must be a string or a regular expression');i.name=r.value,i.nameOp=r.op,i.exact=r.caseSensitive;break}case"include-hidden":{rr(r,[!0,!1]),ar(r,["","="]),i.includeHidden=r.op===""?!0:r.value;break}default:throw new Error(`Unknown attribute "${r.name}", must be one of ${_v.map(l=>`"${l}"`).join(", ")}.`)}return i}function HE(n,e,i){const r=[],l=u=>{if(mt(u)===e.role&&!(e.selected!==void 0&&iv(u)!==e.selected)&&!(e.checked!==void 0&&sv(u)!==e.checked)&&!(e.pressed!==void 0&&rv(u)!==e.pressed)&&!(e.expanded!==void 0&&av(u)!==e.expanded)&&!(e.level!==void 0&&lv(u)!==e.level)&&!(e.disabled!==void 0&&uc(u)!==e.disabled)&&!(!e.includeHidden&&ln(u))){if(e.name!==void 0){const f=At(sl(u,!!e.includeHidden));if(typeof e.name=="string"&&(e.name=At(e.name)),i&&!e.exact&&e.nameOp==="="&&(e.nameOp="*="),!bv(f,{op:e.nameOp||"=",value:e.name,caseSensitive:!!e.exact}))return}r.push(u)}},o=u=>{const f=[];u.shadowRoot&&f.push(u.shadowRoot);for(const h of u.querySelectorAll("*"))l(h),h.shadowRoot&&f.push(h.shadowRoot);f.forEach(o)};return o(n),r}function N0(n){return{queryAll:(e,i)=>{const r=ds(i,!0),l=r.name.toLowerCase();if(!l)throw new Error("Role must not be empty");const o=UE(r.attributes,l);vc();try{return HE(e,o,n)}finally{Sc()}}}}class qE{constructor(){this._retainCacheCounter=0,this._cacheText=new Map,this._cacheQueryCSS=new Map,this._cacheMatches=new Map,this._cacheQuery=new Map,this._cacheMatchesSimple=new Map,this._cacheMatchesParents=new Map,this._cacheCallMatches=new Map,this._cacheCallQuery=new Map,this._cacheQuerySimple=new Map,this._engines=new Map,this._engines.set("not",VE),this._engines.set("is",Ia),this._engines.set("where",Ia),this._engines.set("has",$E),this._engines.set("scope",IE),this._engines.set("light",GE),this._engines.set("visible",KE),this._engines.set("text",YE),this._engines.set("text-is",XE),this._engines.set("text-matches",FE),this._engines.set("has-text",QE),this._engines.set("right-of",za("right-of")),this._engines.set("left-of",za("left-of")),this._engines.set("above",za("above")),this._engines.set("below",za("below")),this._engines.set("near",za("near")),this._engines.set("nth-match",JE);const e=[...this._engines.keys()];e.sort();const i=[...Tb];if(i.sort(),e.join("|")!==i.join("|"))throw new Error(`Please keep customCSSNames in sync with evaluator engines: ${e.join("|")} vs ${i.join("|")}`)}begin(){++this._retainCacheCounter}end(){--this._retainCacheCounter,this._retainCacheCounter||(this._cacheQueryCSS.clear(),this._cacheMatches.clear(),this._cacheQuery.clear(),this._cacheMatchesSimple.clear(),this._cacheMatchesParents.clear(),this._cacheCallMatches.clear(),this._cacheCallQuery.clear(),this._cacheQuerySimple.clear(),this._cacheText.clear())}_cached(e,i,r,l){e.has(i)||e.set(i,[]);const o=e.get(i),u=o.find(h=>r.every((g,y)=>h.rest[y]===g));if(u)return u.result;const f=l();return o.push({rest:r,result:f}),f}_checkSelector(e){if(!(typeof e=="object"&&e&&(Array.isArray(e)||"simples"in e&&e.simples.length)))throw new Error(`Malformed selector "${e}"`);return e}matches(e,i,r){const l=this._checkSelector(i);this.begin();try{return this._cached(this._cacheMatches,e,[l,r.scope,r.pierceShadow,r.originalScope],()=>Array.isArray(l)?this._matchesEngine(Ia,e,l,r):(this._hasScopeClause(l)&&(r=this._expandContextForScopeMatching(r)),this._matchesSimple(e,l.simples[l.simples.length-1].selector,r)?this._matchesParents(e,l,l.simples.length-2,r):!1))}finally{this.end()}}query(e,i){const r=this._checkSelector(i);this.begin();try{return this._cached(this._cacheQuery,r,[e.scope,e.pierceShadow,e.originalScope],()=>{if(Array.isArray(r))return this._queryEngine(Ia,e,r);this._hasScopeClause(r)&&(e=this._expandContextForScopeMatching(e));const l=this._scoreMap;this._scoreMap=new Map;let o=this._querySimple(e,r.simples[r.simples.length-1].selector);return o=o.filter(u=>this._matchesParents(u,r,r.simples.length-2,e)),this._scoreMap.size&&o.sort((u,f)=>{const h=this._scoreMap.get(u),g=this._scoreMap.get(f);return h===g?0:h===void 0?1:g===void 0?-1:h-g}),this._scoreMap=l,o})}finally{this.end()}}_markScore(e,i){this._scoreMap&&this._scoreMap.set(e,i)}_hasScopeClause(e){return e.simples.some(i=>i.selector.functions.some(r=>r.name==="scope"))}_expandContextForScopeMatching(e){if(e.scope.nodeType!==1)return e;const i=bt(e.scope);return i?{...e,scope:i,originalScope:e.originalScope||e.scope}:e}_matchesSimple(e,i,r){return this._cached(this._cacheMatchesSimple,e,[i,r.scope,r.pierceShadow,r.originalScope],()=>{if(e===r.scope||i.css&&!this._matchesCSS(e,i.css))return!1;for(const l of i.functions)if(!this._matchesEngine(this._getEngine(l.name),e,l.args,r))return!1;return!0})}_querySimple(e,i){return i.functions.length?this._cached(this._cacheQuerySimple,i,[e.scope,e.pierceShadow,e.originalScope],()=>{let r=i.css;const l=i.functions;r==="*"&&l.length&&(r=void 0);let o,u=-1;r!==void 0?o=this._queryCSS(e,r):(u=l.findIndex(f=>this._getEngine(f.name).query!==void 0),u===-1&&(u=0),o=this._queryEngine(this._getEngine(l[u].name),e,l[u].args));for(let f=0;fthis._matchesEngine(h,g,l[f].args,e)))}for(let f=0;fthis._matchesEngine(h,g,l[f].args,e)))}return o}):this._queryCSS(e,i.css||"*")}_matchesParents(e,i,r,l){return r<0?!0:this._cached(this._cacheMatchesParents,e,[i,r,l.scope,l.pierceShadow,l.originalScope],()=>{const{selector:o,combinator:u}=i.simples[r];if(u===">"){const f=jo(e,l);return!f||!this._matchesSimple(f,o,l)?!1:this._matchesParents(f,i,r-1,l)}if(u==="+"){const f=dh(e,l);return!f||!this._matchesSimple(f,o,l)?!1:this._matchesParents(f,i,r-1,l)}if(u===""){let f=jo(e,l);for(;f;){if(this._matchesSimple(f,o,l)){if(this._matchesParents(f,i,r-1,l))return!0;if(i.simples[r-1].combinator==="")break}f=jo(f,l)}return!1}if(u==="~"){let f=dh(e,l);for(;f;){if(this._matchesSimple(f,o,l)){if(this._matchesParents(f,i,r-1,l))return!0;if(i.simples[r-1].combinator==="~")break}f=dh(f,l)}return!1}if(u===">="){let f=e;for(;f;){if(this._matchesSimple(f,o,l)){if(this._matchesParents(f,i,r-1,l))return!0;if(i.simples[r-1].combinator==="")break}f=jo(f,l)}return!1}throw new Error(`Unsupported combinator "${u}"`)})}_matchesEngine(e,i,r,l){if(e.matches)return this._callMatches(e,i,r,l);if(e.query)return this._callQuery(e,r,l).includes(i);throw new Error('Selector engine should implement "matches" or "query"')}_queryEngine(e,i,r){if(e.query)return this._callQuery(e,r,i);if(e.matches)return this._queryCSS(i,"*").filter(l=>this._callMatches(e,l,r,i));throw new Error('Selector engine should implement "matches" or "query"')}_callMatches(e,i,r,l){return this._cached(this._cacheCallMatches,i,[e,l.scope,l.pierceShadow,l.originalScope,...r],()=>e.matches(i,r,l,this))}_callQuery(e,i,r){return this._cached(this._cacheCallQuery,e,[r.scope,r.pierceShadow,r.originalScope,...i],()=>e.query(r,i,this))}_matchesCSS(e,i){return e.matches(i)}_queryCSS(e,i){return this._cached(this._cacheQueryCSS,i,[e.scope,e.pierceShadow,e.originalScope],()=>{let r=[];function l(o){if(r=r.concat([...o.querySelectorAll(i)]),!!e.pierceShadow){o.shadowRoot&&l(o.shadowRoot);for(const u of o.querySelectorAll("*"))u.shadowRoot&&l(u.shadowRoot)}}return l(e.scope),r})}_getEngine(e){const i=this._engines.get(e);if(!i)throw new Error(`Unknown selector engine "${e}"`);return i}}const Ia={matches(n,e,i,r){if(e.length===0)throw new Error('"is" engine expects non-empty selector list');return e.some(l=>r.matches(n,l,i))},query(n,e,i){if(e.length===0)throw new Error('"is" engine expects non-empty selector list');let r=[];for(const l of e)r=r.concat(i.query(n,l));return e.length===1?r:Tv(r)}},$E={matches(n,e,i,r){if(e.length===0)throw new Error('"has" engine expects non-empty selector list');return r.query({...i,scope:n},e).length>0}},IE={matches(n,e,i,r){if(e.length!==0)throw new Error('"scope" engine expects no arguments');const l=i.originalScope||i.scope;return l.nodeType===9?n===l.documentElement:n===l},query(n,e,i){if(e.length!==0)throw new Error('"scope" engine expects no arguments');const r=n.originalScope||n.scope;if(r.nodeType===9){const l=r.documentElement;return l?[l]:[]}return r.nodeType===1?[r]:[]}},VE={matches(n,e,i,r){if(e.length===0)throw new Error('"not" engine expects non-empty selector list');return!r.matches(n,e,i)}},GE={query(n,e,i){return i.query({...n,pierceShadow:!1},e)},matches(n,e,i,r){return r.matches(n,e,{...i,pierceShadow:!1})}},KE={matches(n,e,i,r){if(e.length)throw new Error('"visible" engine expects no arguments');return ji(n)}},YE={matches(n,e,i,r){if(e.length!==1||typeof e[0]!="string")throw new Error('"text" engine expects a single string');const l=At(e[0]).toLowerCase(),o=u=>u.normalized.toLowerCase().includes(l);return wc(r._cacheText,n,o)==="self"}},XE={matches(n,e,i,r){if(e.length!==1||typeof e[0]!="string")throw new Error('"text-is" engine expects a single string');const l=At(e[0]),o=u=>!l&&!u.immediate.length?!0:u.immediate.some(f=>At(f)===l);return wc(r._cacheText,n,o)!=="none"}},FE={matches(n,e,i,r){if(e.length===0||typeof e[0]!="string"||e.length>2||e.length===2&&typeof e[1]!="string")throw new Error('"text-matches" engine expects a regexp body and optional regexp flags');const l=new RegExp(e[0],e.length===2?e[1]:void 0),o=u=>l.test(u.full);return wc(r._cacheText,n,o)==="self"}},QE={matches(n,e,i,r){if(e.length!==1||typeof e[0]!="string")throw new Error('"has-text" engine expects a single string');if(Ad(n))return!1;const l=At(e[0]).toLowerCase();return(u=>u.normalized.toLowerCase().includes(l))(Ut(r._cacheText,n))}};function za(n){return{matches(e,i,r,l){const o=i.length&&typeof i[i.length-1]=="number"?i[i.length-1]:void 0,u=o===void 0?i:i.slice(0,i.length-1);if(i.length<1+(o===void 0?0:1))throw new Error(`"${n}" engine expects a selector list and optional maximum distance in pixels`);const f=l.query(r,u),h=mv(n,e,f,o);return h===void 0?!1:(l._markScore(e,h),!0)}}}const JE={query(n,e,i){let r=e[e.length-1];if(e.length<2)throw new Error('"nth-match" engine expects non-empty selector list and an index argument');if(typeof r!="number"||r<1)throw new Error('"nth-match" engine expects a one-based index as the last argument');const l=Ia.query(n,e.slice(0,e.length-1),i);return r--,r1){const h=new Set(f.children);f.children=[];let g=u.firstElementChild;for(;g&&f.children.lengthQo(y)))]}else{const f=rs(r,n,e,i)||Va(n,e,i);l=[Qo(f)]}}const o=l[0],u=n.parseSelector(o);return{selector:o,selectors:l,elements:n.querySelectorAll(u,i.root??e.ownerDocument)}}finally{od(),Sc(),n._evaluator.end()}}function rs(n,e,i,r){if(r.root&&!il(r.root,i))throw new Error("Target element must belong to the root's subtree");if(i===r.root)return[{engine:"css",selector:":scope",score:1}];if(i.ownerDocument.documentElement===i)return[{engine:"css",selector:"html",score:1}];let l=null;const o=f=>{(!l||as(f)as(f.candidate)-as(h.candidate));for(const{candidate:f,isTextCandidate:h}of u){const g=e.querySelectorAll(e.parseSelector(Qo(f)),r.root??i.ownerDocument);if(!g.includes(i))continue;if(g.length===1){o(f);break}const y=g.indexOf(i);if(!(y>5)&&(o([...f,{engine:"nth",selector:String(y),score:Dh}]),!r.isRecursive))for(let m=bt(i);m&&m!==r.root;m=bt(m)){const w=g.filter($=>il(m,$)&&$!==m),v=w.indexOf(i);if(w.length>5||v===-1||v===y&&w.length>1)continue;const E=w.length===1?f:[...f,{engine:"nth",selector:String(v),score:Dh}];if(l&&as([{engine:"",selector:"",score:1},...E])>=as(l))continue;const _=!!r.noText||h,N=_?n.disallowText:n.allowText;let C=N.get(m);C===void 0&&(C=rs(n,e,m,{...r,isRecursive:!0,noText:_})||Va(e,m,r),N.set(m,C)),C&&o([...C,...E])}}return l}function uA(n,e,i){const r=[];{for(const u of["data-testid","data-test-id","data-test"])u!==i.testIdAttributeName&&e.getAttribute(u)&&r.push({engine:"css",selector:`[${u}=${fr(e.getAttribute(u))}]`,score:PE});if(!i.noCSSId){const u=e.getAttribute("id");u&&!hA(u)&&r.push({engine:"css",selector:Lv(u),score:lA})}r.push({engine:"css",selector:Zn(e),score:jv})}if(e.nodeName==="IFRAME"){for(const u of["name","title"])e.getAttribute(u)&&r.push({engine:"css",selector:`${Zn(e)}[${u}=${fr(e.getAttribute(u))}]`,score:ZE});return e.getAttribute(i.testIdAttributeName)&&r.push({engine:"css",selector:`[${i.testIdAttributeName}=${fr(e.getAttribute(i.testIdAttributeName))}]`,score:C0}),zh([r]),r}if(e.getAttribute(i.testIdAttributeName)&&r.push({engine:"internal:testid",selector:`[${i.testIdAttributeName}=${Tt(e.getAttribute(i.testIdAttributeName),!0)}]`,score:C0}),e.nodeName==="INPUT"||e.nodeName==="TEXTAREA"){const u=e;if(u.placeholder){r.push({engine:"internal:attr",selector:`[placeholder=${Tt(u.placeholder,!0)}]`,score:eA});for(const f of pr(u.placeholder))r.push({engine:"internal:attr",selector:`[placeholder=${Tt(f.text,!1)}]`,score:Nv-f.scoreBonus})}}const l=vv(n._evaluator._cacheText,e);for(const u of l){const f=u.normalized;r.push({engine:"internal:label",selector:zt(f,!0),score:tA});for(const h of pr(f))r.push({engine:"internal:label",selector:zt(h.text,!1),score:Cv-h.scoreBonus})}const o=mt(e);return o&&!["none","presentation"].includes(o)&&r.push({engine:"internal:role",selector:o,score:Ov}),e.getAttribute("name")&&["BUTTON","FORM","FIELDSET","FRAME","IFRAME","INPUT","KEYGEN","OBJECT","OUTPUT","SELECT","TEXTAREA","MAP","META","PARAM"].includes(e.nodeName)&&r.push({engine:"css",selector:`${Zn(e)}[name=${fr(e.getAttribute("name"))}]`,score:ph}),["INPUT","TEXTAREA"].includes(e.nodeName)&&e.getAttribute("type")!=="hidden"&&e.getAttribute("type")&&r.push({engine:"css",selector:`${Zn(e)}[type=${fr(e.getAttribute("type"))}]`,score:ph}),["INPUT","TEXTAREA","SELECT"].includes(e.nodeName)&&e.getAttribute("type")!=="hidden"&&r.push({engine:"css",selector:Zn(e),score:ph+1}),zh([r]),r}function fA(n,e,i){if(e.nodeName==="SELECT")return[];const r=[],l=e.getAttribute("title");if(l){r.push([{engine:"internal:attr",selector:`[title=${Tt(l,!0)}]`,score:rA}]);for(const g of pr(l))r.push([{engine:"internal:attr",selector:`[title=${Tt(g.text,!1)}]`,score:Mv-g.scoreBonus}])}const o=e.getAttribute("alt");if(o&&["APPLET","AREA","IMG","INPUT"].includes(e.nodeName)){r.push([{engine:"internal:attr",selector:`[alt=${Tt(o,!0)}]`,score:iA}]);for(const g of pr(o))r.push([{engine:"internal:attr",selector:`[alt=${Tt(g.text,!1)}]`,score:kv-g.scoreBonus}])}const u=Ut(n._evaluator._cacheText,e).normalized,f=u?pr(u):[];if(u){if(i){u.length<=80&&r.push([{engine:"internal:text",selector:zt(u,!0),score:sA}]);for(const y of f)r.push([{engine:"internal:text",selector:zt(y.text,!1),score:Fo-y.scoreBonus}])}const g={engine:"css",selector:Zn(e),score:jv};for(const y of f)r.push([g,{engine:"internal:has-text",selector:zt(y.text,!1),score:Fo-y.scoreBonus}]);if(i&&u.length<=80){const y=new RegExp("^"+rc(u)+"$");r.push([g,{engine:"internal:has-text",selector:zt(y,!1),score:k0}])}}const h=mt(e);if(h&&!["none","presentation"].includes(h)){const g=sl(e,!1);if(g&&!g.match(new RegExp("^\\p{Co}+$","u"))){const y={engine:"internal:role",selector:`${h}[name=${Tt(g,!0)}]`,score:nA};r.push([y]);for(const m of pr(g))r.push([{engine:"internal:role",selector:`${h}[name=${Tt(m.text,!1)}]`,score:Av-m.scoreBonus}])}else{const y={engine:"internal:role",selector:`${h}`,score:Ov};for(const m of f)r.push([y,{engine:"internal:has-text",selector:zt(m.text,!1),score:Fo-m.scoreBonus}]);if(i&&u.length<=80){const m=new RegExp("^"+rc(u)+"$");r.push([y,{engine:"internal:has-text",selector:zt(m,!1),score:k0}])}}}return zh(r),r}function Lv(n){return/^[a-zA-Z][a-zA-Z0-9\-\_]+$/.test(n)?"#"+n:`[id=${fr(n)}]`}function gh(n){return n.some(e=>e.engine==="css"&&(e.selector.startsWith("#")||e.selector.startsWith('[id="')))}function Va(n,e,i){const r=i.root??e.ownerDocument,l=[];function o(f){const h=l.slice();f&&h.unshift(f);const g=h.join(" > "),y=n.parseSelector(g);return n.querySelector(y,r,!1)===e?g:void 0}function u(f){const h={engine:"css",selector:f,score:oA},g=n.parseSelector(f),y=n.querySelectorAll(g,r);if(y.length===1)return[h];const m={engine:"nth",selector:String(y.indexOf(e)),score:Dh};return[h,m]}for(let f=e;f&&f!==r;f=bt(f)){let h="";if(f.id&&!i.noCSSId){const m=Lv(f.id),w=o(m);if(w)return u(w);h=m}const g=f.parentNode,y=[...f.classList].map(dA);for(let m=0;m_.nodeName===w).indexOf(f)===0?Zn(f):`${Zn(f)}:nth-child(${1+m.indexOf(f)})`,x=o(E);if(x)return u(x);h||(h=E)}else h||(h=Zn(f));l.unshift(h)}return u(o())}function zh(n){for(const e of n)for(const i of e)i.score>WE&&i.score>"),i=r,r==="css"?e.push(l):e.push(`${r}=${l}`);return e.join(" ")}function as(n){let e=0;for(let i=0;i="a"&&l<="z"?o="lower":l>="A"&&l<="Z"?o="upper":l>="0"&&l<="9"?o="digit":o="other",o==="lower"&&e==="upper"){e=o;continue}e&&e!==o&&++i,e=o}}return i>=n.length/4}function Lo(n,e){if(n.length<=e)return n;n=n.substring(0,e);const i=n.match(/^(.*)\b(.+?)$/);return i?i[1].trimEnd():""}function pr(n){let e=[];{const i=n.match(/^([\d.,]+)[^.,\w]/),r=i?i[1].length:0;if(r){const l=Lo(n.substring(r).trimStart(),80);e.push({text:l,scoreBonus:l.length<=30?2:1})}}{const i=n.match(/[^.,\w]([\d.,]+)$/),r=i?i[1].length:0;if(r){const l=Lo(n.substring(0,n.length-r).trimEnd(),80);e.push({text:l,scoreBonus:l.length<=30?2:1})}}return n.length<=30?e.push({text:n,scoreBonus:0}):(e.push({text:Lo(n,80),scoreBonus:0}),e.push({text:Lo(n,30),scoreBonus:1})),e=e.filter(i=>i.text),e.length||e.push({text:n.substring(0,80),scoreBonus:0}),e}function Zn(n){return n.nodeName.toLocaleLowerCase().replace(/[:\.]/g,e=>"\\"+e)}function dA(n){let e="";for(let i=0;i=1&&i<=31||i>=48&&i<=57&&(e===0||e===1&&n.charCodeAt(0)===45)?"\\"+i.toString(16)+" ":e===0&&i===45&&n.length===1?"\\"+n.charAt(e):i>=128||i===45||i===95||i>=48&&i<=57||i>=65&&i<=90||i>=97&&i<=122?n.charAt(e):"\\"+n.charAt(e)}function Rv(n,e){const i=n.replace(/^[a-zA-Z]:/,"").replace(/\\/g,"/");let r=i.substring(i.lastIndexOf("/")+1);return r.endsWith(e)&&(r=r.substring(0,r.length-e.length)),r}function gA(n,e){return e?e.toUpperCase():""}const mA=/(?:^|[-_/])(\w)/g,Dv=n=>n&&n.replace(mA,gA);function yA(n){function e(y){const m=y.name||y._componentTag||y.__playwright_guessedName;if(m)return m;const w=y.__file;if(w)return Dv(Rv(w,".vue"))}function i(y,m){return y.type.__playwright_guessedName=m,m}function r(y){var w,v,E,x;const m=e(y.type||{});if(m)return m;if(y.root===y)return"Root";for(const _ in(v=(w=y.parent)==null?void 0:w.type)==null?void 0:v.components)if(((E=y.parent)==null?void 0:E.type.components[_])===y.type)return i(y,_);for(const _ in(x=y.appContext)==null?void 0:x.components)if(y.appContext.components[_]===y.type)return i(y,_);return"Anonymous Component"}function l(y){return y._isBeingDestroyed||y.isUnmounted}function o(y){return y.subTree.type.toString()==="Symbol(Fragment)"}function u(y){const m=[];return y.component&&m.push(y.component),y.suspense&&m.push(...u(y.suspense.activeBranch)),Array.isArray(y.children)&&y.children.forEach(w=>{w.component?m.push(w.component):m.push(...u(w))}),m.filter(w=>{var v;return!l(w)&&!((v=w.type.devtools)!=null&&v.hide)})}function f(y){return o(y)?h(y.subTree):[y.subTree.el]}function h(y){if(!y.children)return[];const m=[];for(let w=0,v=y.children.length;w!!u.component).map(u=>u.component):[]}function l(o){return{name:i(o),children:r(o).map(l),rootElements:[o.$el],props:o._props}}return l(n)}function zv(n,e,i=[]){e(n)&&i.push(n);for(const r of n.children)zv(r,e,i);return i}function Bv(n,e=[]){const r=(n.ownerDocument||n).createTreeWalker(n,NodeFilter.SHOW_ELEMENT),l=new Set;do{const o=r.currentNode;o.__vue__&&l.add(o.__vue__.$root),o.__vue_app__&&o._vnode&&o._vnode.component&&e.push({root:o._vnode.component,version:3});const u=o instanceof Element?o.shadowRoot:null;u&&Bv(u,e)}while(r.nextNode());for(const o of l)e.push({version:2,root:o});return e}const vA=()=>({queryAll(n,e){const i=n.ownerDocument||n,{name:r,attributes:l}=ds(e,!1),f=Bv(i).map(g=>g.version===3?yA(g.root):bA(g.root)).map(g=>zv(g,y=>{if(r&&y.name!==r||y.rootElements.some(m=>!il(n,m)))return!1;for(const m of l)if(!yv(y.props,m))return!1;return!0})).flat(),h=new Set;for(const g of f)for(const y of g.rootElements)h.add(y);return[...h]}}),O0={queryAll(n,e){e.startsWith("/")&&n.nodeType!==Node.DOCUMENT_NODE&&(e="."+e);const i=[],r=n.ownerDocument||n;if(!r)return i;const l=r.evaluate(e,n,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE);for(let o=l.iterateNext();o;o=l.iterateNext())o.nodeType===Node.ELEMENT_NODE&&i.push(o);return i}};function Nd(n,e,i){return`internal:attr=[${n}=${Tt(e,(i==null?void 0:i.exact)||!1)}]`}function SA(n,e){return`internal:testid=[${n}=${Tt(e,!0)}]`}function wA(n,e){return"internal:label="+zt(n,!!(e!=null&&e.exact))}function xA(n,e){return Nd("alt",n,e)}function _A(n,e){return Nd("title",n,e)}function TA(n,e){return Nd("placeholder",n,e)}function EA(n,e){return"internal:text="+zt(n,!!(e!=null&&e.exact))}function AA(n,e={}){const i=[];return e.checked!==void 0&&i.push(["checked",String(e.checked)]),e.disabled!==void 0&&i.push(["disabled",String(e.disabled)]),e.selected!==void 0&&i.push(["selected",String(e.selected)]),e.expanded!==void 0&&i.push(["expanded",String(e.expanded)]),e.includeHidden!==void 0&&i.push(["include-hidden",String(e.includeHidden)]),e.level!==void 0&&i.push(["level",String(e.level)]),e.name!==void 0&&i.push(["name",Tt(e.name,!!e.exact)]),e.pressed!==void 0&&i.push(["pressed",String(e.pressed)]),`internal:role=${n}${i.map(([r,l])=>`[${r}=${l}]`).join("")}`}const Ba=Symbol("selector"),NA=class Ga{constructor(e,i,r){if(r!=null&&r.hasText&&(i+=` >> internal:has-text=${zt(r.hasText,!1)}`),r!=null&&r.hasNotText&&(i+=` >> internal:has-not-text=${zt(r.hasNotText,!1)}`),r!=null&&r.has&&(i+=" >> internal:has="+JSON.stringify(r.has[Ba])),r!=null&&r.hasNot&&(i+=" >> internal:has-not="+JSON.stringify(r.hasNot[Ba])),(r==null?void 0:r.visible)!==void 0&&(i+=` >> visible=${r.visible?"true":"false"}`),this[Ba]=i,i){const u=e.parseSelector(i);this.element=e.querySelector(u,e.document,!1),this.elements=e.querySelectorAll(u,e.document)}const l=i,o=this;o.locator=(u,f)=>new Ga(e,l?l+" >> "+u:u,f),o.getByTestId=u=>o.locator(SA(e.testIdAttributeNameForStrictErrorAndConsoleCodegen(),u)),o.getByAltText=(u,f)=>o.locator(xA(u,f)),o.getByLabel=(u,f)=>o.locator(wA(u,f)),o.getByPlaceholder=(u,f)=>o.locator(TA(u,f)),o.getByText=(u,f)=>o.locator(EA(u,f)),o.getByTitle=(u,f)=>o.locator(_A(u,f)),o.getByRole=(u,f={})=>o.locator(AA(u,f)),o.filter=u=>new Ga(e,i,u),o.first=()=>o.locator("nth=0"),o.last=()=>o.locator("nth=-1"),o.nth=u=>o.locator(`nth=${u}`),o.and=u=>new Ga(e,l+" >> internal:and="+JSON.stringify(u[Ba])),o.or=u=>new Ga(e,l+" >> internal:or="+JSON.stringify(u[Ba]))}};let CA=NA;class kA{constructor(e){this._injectedScript=e}install(){this._injectedScript.window.playwright||(this._injectedScript.window.playwright={$:(e,i)=>this._querySelector(e,!!i),$$:e=>this._querySelectorAll(e),inspect:e=>this._inspect(e),selector:e=>this._selector(e),generateLocator:(e,i)=>this._generateLocator(e,i),ariaSnapshot:(e,i)=>this._injectedScript.ariaSnapshot(e||this._injectedScript.document.body,i||{mode:"expect"}),resume:()=>this._resume(),...new CA(this._injectedScript,"")},delete this._injectedScript.window.playwright.filter,delete this._injectedScript.window.playwright.first,delete this._injectedScript.window.playwright.last,delete this._injectedScript.window.playwright.nth,delete this._injectedScript.window.playwright.and,delete this._injectedScript.window.playwright.or)}_querySelector(e,i){if(typeof e!="string")throw new Error("Usage: playwright.query('Playwright >> selector').");const r=this._injectedScript.parseSelector(e);return this._injectedScript.querySelector(r,this._injectedScript.document,i)}_querySelectorAll(e){if(typeof e!="string")throw new Error("Usage: playwright.$$('Playwright >> selector').");const i=this._injectedScript.parseSelector(e);return this._injectedScript.querySelectorAll(i,this._injectedScript.document)}_inspect(e){if(typeof e!="string")throw new Error("Usage: playwright.inspect('Playwright >> selector').");this._injectedScript.window.inspect(this._querySelector(e,!1))}_selector(e){if(!(e instanceof Element))throw new Error("Usage: playwright.selector(element).");return this._injectedScript.generateSelectorSimple(e)}_generateLocator(e,i){if(!(e instanceof Element))throw new Error("Usage: playwright.locator(element).");const r=this._injectedScript.generateSelectorSimple(e);return Oi(i||"javascript",r)}_resume(){if(!this._injectedScript.window.__pw_resume)return!1;this._injectedScript.window.__pw_resume().catch(()=>{})}}function MA(n){try{return n instanceof RegExp||Object.prototype.toString.call(n)==="[object RegExp]"}catch{return!1}}function OA(n){try{return n instanceof Date||Object.prototype.toString.call(n)==="[object Date]"}catch{return!1}}function jA(n){try{return n instanceof URL||Object.prototype.toString.call(n)==="[object URL]"}catch{return!1}}function LA(n){var e;try{return n instanceof Error||n&&((e=Object.getPrototypeOf(n))==null?void 0:e.name)==="Error"}catch{return!1}}function RA(n,e){try{return n instanceof e||Object.prototype.toString.call(n)===`[object ${e.name}]`}catch{return!1}}const Uv={i8:Int8Array,ui8:Uint8Array,ui8c:Uint8ClampedArray,i16:Int16Array,ui16:Uint16Array,i32:Int32Array,ui32:Uint32Array,f32:Float32Array,f64:Float64Array,bi64:BigInt64Array,bui64:BigUint64Array};function DA(n){if("toBase64"in n)return n.toBase64();const e=Array.from(new Uint8Array(n.buffer,n.byteOffset,n.byteLength)).map(i=>String.fromCharCode(i)).join("");return btoa(e)}function zA(n,e){const i=atob(n),r=new Uint8Array(i.length);for(let l=0;l";if(typeof globalThis.Document=="function"&&n instanceof globalThis.Document)return"ref: ";if(typeof globalThis.Node=="function"&&n instanceof globalThis.Node)return"ref: "}return Hv(n,e,i)}function Hv(n,e,i){var o;const r=e(n);if("fallThrough"in r)n=r.fallThrough;else return r;if(typeof n=="symbol")return{v:"undefined"};if(Object.is(n,void 0))return{v:"undefined"};if(Object.is(n,null))return{v:"null"};if(Object.is(n,NaN))return{v:"NaN"};if(Object.is(n,1/0))return{v:"Infinity"};if(Object.is(n,-1/0))return{v:"-Infinity"};if(Object.is(n,-0))return{v:"-0"};if(typeof n=="boolean"||typeof n=="number"||typeof n=="string")return n;if(typeof n=="bigint")return{bi:n.toString()};if(LA(n)){let u;return(o=n.stack)!=null&&o.startsWith(n.name+": "+n.message)?u=n.stack:u=`${n.name}: ${n.message} -${n.stack}`,{e:{n:n.name,m:n.message,s:u}}}if(OA(n))return{d:n.toJSON()};if(jA(n))return{u:n.toJSON()};if(MA(n))return{r:{p:n.source,f:n.flags}};for(const[u,f]of Object.entries(Uv))if(RA(n,f))return{ta:{b:DA(n),k:u}};const l=i.visited.get(n);if(l)return{ref:l};if(Array.isArray(n)){const u=[],f=++i.lastId;i.visited.set(n,f);for(let h=0;h({fallThrough:r}))}_promiseAwareJsonValueNoThrow(e){const i=r=>{try{return this.jsonValue(!0,r)}catch{return}};return e&&typeof e=="object"&&typeof e.then=="function"?(async()=>{const r=await e;return i(r)})():i(e)}}class qv{constructor(e,i){this._testIdAttributeNameForStrictErrorAndConsoleCodegen="data-testid",this._lastAriaSnapshotForTrack=new Map,this.utils={asLocator:Oi,cacheNormalizedWhitespaces:r_,elementText:Ut,getAriaRole:mt,getElementAccessibleDescription:S0,getElementAccessibleName:sl,isElementVisible:ji,isInsideScope:il,normalizeWhiteSpace:At,parseAriaSnapshot:id,generateAriaTree:Ja,findNewElement:AE,builtins:null},this.window=e,this.document=e.document,this.isUnderTest=i.isUnderTest,this.utils.builtins=new UA(e,i.isUnderTest).builtins,this._sdkLanguage=i.sdkLanguage,this._testIdAttributeNameForStrictErrorAndConsoleCodegen=i.testIdAttributeName,this._evaluator=new qE,this.consoleApi=new kA(this),this.onGlobalListenersRemoved=new Set,this._autoClosingTags=new Set(["AREA","BASE","BR","COL","COMMAND","EMBED","HR","IMG","INPUT","KEYGEN","LINK","MENUITEM","META","PARAM","SOURCE","TRACK","WBR"]),this._booleanAttributes=new Set(["checked","selected","disabled","readonly","multiple"]),this._eventTypes=new Map([["auxclick","mouse"],["click","mouse"],["dblclick","mouse"],["mousedown","mouse"],["mouseeenter","mouse"],["mouseleave","mouse"],["mousemove","mouse"],["mouseout","mouse"],["mouseover","mouse"],["mouseup","mouse"],["mouseleave","mouse"],["mousewheel","mouse"],["keydown","keyboard"],["keyup","keyboard"],["keypress","keyboard"],["textInput","keyboard"],["touchstart","touch"],["touchmove","touch"],["touchend","touch"],["touchcancel","touch"],["pointerover","pointer"],["pointerout","pointer"],["pointerenter","pointer"],["pointerleave","pointer"],["pointerdown","pointer"],["pointerup","pointer"],["pointermove","pointer"],["pointercancel","pointer"],["gotpointercapture","pointer"],["lostpointercapture","pointer"],["focus","focus"],["blur","focus"],["drag","drag"],["dragstart","drag"],["dragend","drag"],["dragover","drag"],["dragenter","drag"],["dragleave","drag"],["dragexit","drag"],["drop","drag"],["wheel","wheel"],["deviceorientation","deviceorientation"],["deviceorientationabsolute","deviceorientation"],["devicemotion","devicemotion"]]),this._hoverHitTargetInterceptorEvents=new Set(["mousemove"]),this._tapHitTargetInterceptorEvents=new Set(["pointerdown","pointerup","touchstart","touchend","touchcancel"]),this._mouseHitTargetInterceptorEvents=new Set(["mousedown","mouseup","pointerdown","pointerup","click","auxclick","dblclick","contextmenu"]),this._allHitTargetInterceptorEvents=new Set([...this._hoverHitTargetInterceptorEvents,...this._tapHitTargetInterceptorEvents,...this._mouseHitTargetInterceptorEvents]),this._engines=new Map,this._engines.set("xpath",O0),this._engines.set("xpath:light",O0),this._engines.set("_react",BE()),this._engines.set("_vue",vA()),this._engines.set("role",N0(!1)),this._engines.set("text",this._createTextEngine(!0,!1)),this._engines.set("text:light",this._createTextEngine(!1,!1)),this._engines.set("id",this._createAttributeEngine("id",!0)),this._engines.set("id:light",this._createAttributeEngine("id",!1)),this._engines.set("data-testid",this._createAttributeEngine("data-testid",!0)),this._engines.set("data-testid:light",this._createAttributeEngine("data-testid",!1)),this._engines.set("data-test-id",this._createAttributeEngine("data-test-id",!0)),this._engines.set("data-test-id:light",this._createAttributeEngine("data-test-id",!1)),this._engines.set("data-test",this._createAttributeEngine("data-test",!0)),this._engines.set("data-test:light",this._createAttributeEngine("data-test",!1)),this._engines.set("css",this._createCSSEngine()),this._engines.set("nth",{queryAll:()=>[]}),this._engines.set("visible",this._createVisibleEngine()),this._engines.set("internal:control",this._createControlEngine()),this._engines.set("internal:has",this._createHasEngine()),this._engines.set("internal:has-not",this._createHasNotEngine()),this._engines.set("internal:and",{queryAll:()=>[]}),this._engines.set("internal:or",{queryAll:()=>[]}),this._engines.set("internal:chain",this._createInternalChainEngine()),this._engines.set("internal:label",this._createInternalLabelEngine()),this._engines.set("internal:text",this._createTextEngine(!0,!0)),this._engines.set("internal:has-text",this._createInternalHasTextEngine()),this._engines.set("internal:has-not-text",this._createInternalHasNotTextEngine()),this._engines.set("internal:attr",this._createNamedAttributeEngine()),this._engines.set("internal:testid",this._createNamedAttributeEngine()),this._engines.set("internal:role",N0(!0)),this._engines.set("internal:describe",this._createDescribeEngine()),this._engines.set("aria-ref",this._createAriaRefEngine());for(const{name:r,source:l}of i.customEngines)this._engines.set(r,this.eval(l));this._stableRafCount=i.stableRafCount,this._browserName=i.browserName,this._isUtilityWorld=!!i.isUtilityWorld,FT({browserNameForWorkarounds:i.browserName}),this._setupGlobalListenersRemovalDetection(),this._setupHitTargetInterceptors(),this.isUnderTest&&(this.window.__injectedScript=this)}eval(e){return this.window.eval(e)}testIdAttributeNameForStrictErrorAndConsoleCodegen(){return this._testIdAttributeNameForStrictErrorAndConsoleCodegen}parseSelector(e){const i=cl(e);return i_(i,r=>{if(!this._engines.has(r.name))throw this.createStacklessError(`Unknown engine "${r.name}" while parsing selector ${e}`)}),i}generateSelector(e,i){return M0(this,e,i)}generateSelectorSimple(e,i){return M0(this,e,{...i,testIdAttributeName:this._testIdAttributeNameForStrictErrorAndConsoleCodegen}).selector}querySelector(e,i,r){const l=this.querySelectorAll(e,i);if(r&&l.length>1)throw this.strictModeViolationError(e,l);return this.checkDeprecatedSelectorUsage(e,l),l[0]}_queryNth(e,i){const r=[...e];let l=+i.body;return l===-1&&(l=r.length-1),new Set(r.slice(l,l+1))}_queryLayoutSelector(e,i,r){const l=i.name,o=i.body,u=[],f=this.querySelectorAll(o.parsed,r);for(const h of e){const g=mv(l,h,f,o.distance);g!==void 0&&u.push({element:h,score:g})}return u.sort((h,g)=>h.score-g.score),new Set(u.map(h=>h.element))}ariaSnapshot(e,i){return this.incrementalAriaSnapshot(e,i).full}incrementalAriaSnapshot(e,i){if(e.nodeType!==Node.ELEMENT_NODE)throw this.createStacklessError("Can only capture aria snapshot of Element nodes.");const r=Ja(e,i),l=Pa(r,i);let o;if(i.track){const u=this._lastAriaSnapshotForTrack.get(i.track);u&&(o=Pa(r,i,u)),this._lastAriaSnapshotForTrack.set(i.track,r)}return this._lastAriaSnapshotForQuery=r,{full:l,incremental:o,iframeRefs:r.iframeRefs}}ariaSnapshotForRecorder(){const e=Ja(this.document.body,{mode:"ai"});return{ariaSnapshot:Pa(e,{mode:"ai"}),refs:e.refs}}getAllElementsMatchingExpectAriaTemplate(e,i){return wE(e.documentElement,i)}querySelectorAll(e,i){if(e.capture!==void 0){if(e.parts.some(l=>l.name==="nth"))throw this.createStacklessError("Can't query n-th element in a request with the capture.");const r={parts:e.parts.slice(0,e.capture+1)};if(e.capturer.has(u)))}else if(l.name==="internal:or"){const o=this.querySelectorAll(l.body.parsed,i);r=new Set(Tv(new Set([...r,...o])))}else if(jE.includes(l.name))r=this._queryLayoutSelector(r,l,i);else{const o=new Set;for(const u of r){const f=this._queryEngineAll(l,u);for(const h of f)o.add(h)}r=o}return[...r]}finally{this._evaluator.end()}}_queryEngineAll(e,i){const r=this._engines.get(e.name).queryAll(i,e.body);for(const l of r)if(!("nodeName"in l))throw this.createStacklessError(`Expected a Node but got ${Object.prototype.toString.call(l)}`);return r}_createAttributeEngine(e,i){const r=l=>[{simples:[{selector:{css:`[${e}=${JSON.stringify(l)}]`,functions:[]},combinator:""}]}];return{queryAll:(l,o)=>this._evaluator.query({scope:l,pierceShadow:i},r(o))}}_createCSSEngine(){return{queryAll:(e,i)=>this._evaluator.query({scope:e,pierceShadow:!0},i)}}_createTextEngine(e,i){return{queryAll:(l,o)=>{const{matcher:u,kind:f}=Do(o,i),h=[];let g=null;const y=w=>{if(f==="lax"&&g&&g.contains(w))return!1;const v=wc(this._evaluator._cacheText,w,u);v==="none"&&(g=w),(v==="self"||v==="selfAndChildren"&&f==="strict"&&!i)&&h.push(w)};l.nodeType===Node.ELEMENT_NODE&&y(l);const m=this._evaluator._queryCSS({scope:l,pierceShadow:e},"*");for(const w of m)y(w);return h}}}_createInternalHasTextEngine(){return{queryAll:(e,i)=>{if(e.nodeType!==1)return[];const r=e,l=Ut(this._evaluator._cacheText,r),{matcher:o}=Do(i,!0);return o(l)?[r]:[]}}}_createInternalHasNotTextEngine(){return{queryAll:(e,i)=>{if(e.nodeType!==1)return[];const r=e,l=Ut(this._evaluator._cacheText,r),{matcher:o}=Do(i,!0);return o(l)?[]:[r]}}}_createInternalLabelEngine(){return{queryAll:(e,i)=>{const{matcher:r}=Do(i,!0);return this._evaluator._queryCSS({scope:e,pierceShadow:!0},"*").filter(o=>vv(this._evaluator._cacheText,o).some(u=>r(u)))}}}_createNamedAttributeEngine(){return{queryAll:(i,r)=>{const l=ds(r,!0);if(l.name||l.attributes.length!==1)throw new Error("Malformed attribute selector: "+r);const{name:o,value:u,caseSensitive:f}=l.attributes[0],h=f?null:u.toLowerCase();let g;return u instanceof RegExp?g=m=>!!m.match(u):f?g=m=>m===u:g=m=>m.toLowerCase().includes(h),this._evaluator._queryCSS({scope:i,pierceShadow:!0},`[${o}]`).filter(m=>g(m.getAttribute(o)))}}}_createDescribeEngine(){return{queryAll:i=>i.nodeType!==1?[]:[i]}}_createControlEngine(){return{queryAll(e,i){if(i==="enter-frame")return[];if(i==="return-empty")return[];if(i==="component")return e.nodeType!==1?[]:[e.childElementCount===1?e.firstElementChild:e];throw new Error(`Internal error, unknown internal:control selector ${i}`)}}}_createHasEngine(){return{queryAll:(i,r)=>i.nodeType!==1?[]:!!this.querySelector(r.parsed,i,!1)?[i]:[]}}_createHasNotEngine(){return{queryAll:(i,r)=>i.nodeType!==1?[]:!!this.querySelector(r.parsed,i,!1)?[]:[i]}}_createVisibleEngine(){return{queryAll:(i,r)=>{if(i.nodeType!==1)return[];const l=r==="true";return ji(i)===l?[i]:[]}}}_createInternalChainEngine(){return{queryAll:(i,r)=>this.querySelectorAll(r.parsed,i)}}extend(e,i){const r=this.window.eval(` - (() => { - const module = {}; - ${e} - return module.exports.default(); - })()`);return new r(this,i)}async viewportRatio(e){return await new Promise(i=>{const r=new IntersectionObserver(l=>{i(l[0].intersectionRatio),r.disconnect()});r.observe(e),this.utils.builtins.requestAnimationFrame(()=>{})})}getElementBorderWidth(e){if(e.nodeType!==Node.ELEMENT_NODE||!e.ownerDocument||!e.ownerDocument.defaultView)return{left:0,top:0};const i=e.ownerDocument.defaultView.getComputedStyle(e);return{left:parseInt(i.borderLeftWidth||"",10),top:parseInt(i.borderTopWidth||"",10)}}describeIFrameStyle(e){if(!e.ownerDocument||!e.ownerDocument.defaultView)return"error:notconnected";const i=e.ownerDocument.defaultView;for(let l=e;l;l=bt(l))if(i.getComputedStyle(l).transform!=="none")return"transformed";const r=i.getComputedStyle(e);return{left:parseInt(r.borderLeftWidth||"",10)+parseInt(r.paddingLeft||"",10),top:parseInt(r.borderTopWidth||"",10)+parseInt(r.paddingTop||"",10)}}retarget(e,i){let r=e.nodeType===Node.ELEMENT_NODE?e:e.parentElement;if(!r)return null;if(i==="none")return r;if(!r.matches("input, textarea, select")&&!r.isContentEditable&&(i==="button-link"?r=r.closest("button, [role=button], a, [role=link]")||r:r=r.closest("button, [role=button], [role=checkbox], [role=radio]")||r),i==="follow-label"&&!r.matches("a, input, textarea, button, select, [role=link], [role=button], [role=checkbox], [role=radio]")&&!r.isContentEditable){const l=r.closest("label");l&&l.control&&(r=l.control)}return r}async checkElementStates(e,i){if(i.includes("stable")){const r=await this._checkElementIsStable(e);if(r===!1)return{missingState:"stable"};if(r==="error:notconnected")return"error:notconnected"}for(const r of i)if(r!=="stable"){const l=this.elementState(e,r);if(l.received==="error:notconnected")return"error:notconnected";if(!l.matches)return{missingState:r}}}async _checkElementIsStable(e){const i=Symbol("continuePolling");let r,l=0,o=0;const u=()=>{const m=this.retarget(e,"no-follow-label");if(!m)return"error:notconnected";const w=this.utils.builtins.performance.now();if(this._stableRafCount>1&&w-o<15)return i;o=w;const v=m.getBoundingClientRect(),E={x:v.top,y:v.left,width:v.width,height:v.height};if(r){if(!(E.x===r.x&&E.y===r.y&&E.width===r.width&&E.height===r.height))return!1;if(++l>=this._stableRafCount)return!0}return r=E,i};let f,h;const g=new Promise((m,w)=>{f=m,h=w}),y=()=>{try{const m=u();m!==i?f(m):this.utils.builtins.requestAnimationFrame(y)}catch(m){h(m)}};return this.utils.builtins.requestAnimationFrame(y),g}_createAriaRefEngine(){return{queryAll:(i,r)=>{var o,u;const l=(u=(o=this._lastAriaSnapshotForQuery)==null?void 0:o.elements)==null?void 0:u.get(r);return l&&l.isConnected?[l]:[]}}}elementState(e,i){const r=this.retarget(e,["visible","hidden"].includes(i)?"none":"follow-label");if(!r||!r.isConnected)return i==="hidden"?{matches:!0,received:"hidden"}:{matches:!1,received:"error:notconnected"};if(i==="visible"||i==="hidden"){const l=ji(r);return{matches:i==="visible"?l:!l,received:l?"visible":"hidden"}}if(i==="disabled"||i==="enabled"){const l=uc(r);return{matches:i==="disabled"?l:!l,received:l?"disabled":"enabled"}}if(i==="editable"){const l=uc(r),o=cE(r);if(o==="error")throw this.createStacklessError("Element is not an ,