chore: remove test artifacts, fix hardcoded paths, ruff clean, license
- Remove committed .coverage and test-results/ (196K screenshots); gitignore them - Fix hardcoded /home/userpath + venv/bin/python in test_endpoints.py (relative backend dir + sys.executable) - Fix concatenated 'import json' in settings.py; bare excepts -> Exception; SQLAlchemy-safe is_active.is_(True); __all__ on models/schemas barrels - ruff clean (93 fixes), MIT LICENSE, PLAN.md -> docs/, README Tests section - 300 tests pass, 93.7% coverage
This commit is contained in:
parent
85d68a9313
commit
5c283eceef
4
.gitignore
vendored
4
.gitignore
vendored
@ -21,4 +21,6 @@ api-key.json
|
|||||||
music/*
|
music/*
|
||||||
uploads/*
|
uploads/*
|
||||||
static/*
|
static/*
|
||||||
backend/app.db
|
backend/app.dbcoverage/
|
||||||
|
test-results/
|
||||||
|
.coverage
|
||||||
|
|||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Jarian Cottingham
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
45
README.md
45
README.md
@ -35,3 +35,48 @@ Key settings:
|
|||||||
- `web/` — React web frontend (Vite + Tailwind)
|
- `web/` — React web frontend (Vite + Tailwind)
|
||||||
- `mobile/` — React Native mobile app (Expo)
|
- `mobile/` — React Native mobile app (Expo)
|
||||||
- `shared/` — Shared TypeScript types and API client
|
- `shared/` — Shared TypeScript types and API client
|
||||||
|
- `e2e/` — Playwright end-to-end specs
|
||||||
|
- `docs/` — Implementation plan
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
**Backend** (pytest, 90%+ coverage gate):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt pytest-cov
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
300 tests covering endpoints, routers, services, schemas, and models.
|
||||||
|
|
||||||
|
**E2E** (Playwright):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npx playwright install
|
||||||
|
npm run dev:backend & # start backend on :8000
|
||||||
|
npx playwright test
|
||||||
|
```
|
||||||
|
|
||||||
|
**TypeScript** (web + mobile + shared workspaces):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run typecheck
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
npm workspaces scripts at the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # web + backend together
|
||||||
|
npm run dev:web # Vite dev server only
|
||||||
|
npm run build:web # production web build
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see [LICENSE](LICENSE).
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import functools
|
|
||||||
from fastapi import FastAPI, Request, HTTPException, Depends
|
from fastapi import FastAPI, Request, HTTPException, Depends
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
import os
|
|
||||||
|
|
||||||
from .db.database import init_db
|
from .db.database import init_db
|
||||||
from .routers import (
|
from .routers import (
|
||||||
@ -84,7 +82,6 @@ app.include_router(account.router)
|
|||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
def startup():
|
def startup():
|
||||||
init_db()
|
init_db()
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from .db.database import SessionLocal
|
from .db.database import SessionLocal
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -6,4 +6,20 @@ from .radio import RadioStation
|
|||||||
from .shareplay import SharePlayRoom, SharePlayCue
|
from .shareplay import SharePlayRoom, SharePlayCue
|
||||||
from .settings import UserSetting
|
from .settings import UserSetting
|
||||||
from .releases import NewReleaseCheck
|
from .releases import NewReleaseCheck
|
||||||
from .events import ConcertEvent
|
from .events import ConcertEvent
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Song",
|
||||||
|
"Playlist",
|
||||||
|
"PlaylistSong",
|
||||||
|
"MoodCategory",
|
||||||
|
"MoodSong",
|
||||||
|
"LyricsCache",
|
||||||
|
"LofiChannel",
|
||||||
|
"RadioStation",
|
||||||
|
"SharePlayRoom",
|
||||||
|
"SharePlayCue",
|
||||||
|
"UserSetting",
|
||||||
|
"NewReleaseCheck",
|
||||||
|
"ConcertEvent",
|
||||||
|
]
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, String, Float, DateTime
|
from sqlalchemy import Column, String, Float
|
||||||
from ..db.database import Base
|
from ..db.database import Base
|
||||||
|
|
||||||
class ConcertEvent(Base):
|
class ConcertEvent(Base):
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from ..db.database import get_db
|
from ..db.database import get_db
|
||||||
from ..schemas.account import AccountStatsResponse, ListeningHistoryItem
|
from ..schemas.account import AccountStatsResponse
|
||||||
from ..models.song import Song
|
from ..models.song import Song
|
||||||
from ..models.playlist import Playlist
|
from ..models.playlist import Playlist
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from ..schemas.events import ConcertEventResponse
|
from ..schemas.events import ConcertEventResponse
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
from fastapi import APIRouter, Depends, UploadFile, File
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List
|
from typing import List
|
||||||
from ..db.database import get_db
|
from ..db.database import get_db
|
||||||
from ..schemas.song import ScanResult
|
from ..schemas.song import ScanResult
|
||||||
from ..services.audio import scan_directory, extract_metadata, transcode_to_ogg
|
from ..services.audio import extract_metadata, transcode_to_ogg
|
||||||
from ..models.song import Song
|
from ..models.song import Song
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
|
|||||||
@ -25,7 +25,7 @@ DEFAULT_CHANNELS = [
|
|||||||
|
|
||||||
@router.get("/channels", response_model=List[LofiChannelResponse])
|
@router.get("/channels", response_model=List[LofiChannelResponse])
|
||||||
def list_channels(db: Session = Depends(get_db)):
|
def list_channels(db: Session = Depends(get_db)):
|
||||||
channels = db.query(LofiChannel).filter(LofiChannel.is_active == True).all()
|
channels = db.query(LofiChannel).filter(LofiChannel.is_active.is_(True)).all()
|
||||||
return [LofiChannelResponse.model_validate(c) for c in channels]
|
return [LofiChannelResponse.model_validate(c) for c in channels]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@ -6,7 +6,6 @@ from ..db.database import get_db
|
|||||||
from ..models.mood import MoodCategory
|
from ..models.mood import MoodCategory
|
||||||
from ..schemas.mood import MoodCategoryResponse, MoodPlaylistResponse
|
from ..schemas.mood import MoodCategoryResponse, MoodPlaylistResponse
|
||||||
from ..services.mood_engine import analyze_song_mood, get_mood_playlist, seed_mood_categories
|
from ..services.mood_engine import analyze_song_mood, get_mood_playlist, seed_mood_categories
|
||||||
from ..services.audio import get_stream_path
|
|
||||||
|
|
||||||
|
|
||||||
class SetMoodRequest(BaseModel):
|
class SetMoodRequest(BaseModel):
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List, Optional
|
from typing import List
|
||||||
from ..db.database import get_db
|
from ..db.database import get_db
|
||||||
from ..models.playlist import Playlist, PlaylistSong
|
from ..models.playlist import Playlist, PlaylistSong
|
||||||
from ..models.song import Song
|
from ..models.song import Song
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Query
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import httpx
|
import httpx
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from ..db.database import get_db
|
from ..db.database import get_db
|
||||||
from ..models.song import Song
|
from ..models.song import Song
|
||||||
from ..models.releases import NewReleaseCheck
|
|
||||||
from ..schemas.releases import NewReleaseResponse
|
from ..schemas.releases import NewReleaseResponse
|
||||||
from ..services.musicbrainz import search_artist, get_artist_releases
|
from ..services.musicbrainz import search_artist, get_artist_releases
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
@ -97,7 +98,4 @@ def remove_server(server_id: str, db: Session = Depends(get_db)):
|
|||||||
setting.value = json.dumps(dirs)
|
setting.value = json.dumps(dirs)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return {"message": "Server removed"}
|
return {"message": "Server removed"}
|
||||||
|
|
||||||
|
|
||||||
import json
|
|
||||||
@ -4,8 +4,7 @@ from sqlalchemy.orm import Session
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from ..db.database import get_db
|
from ..db.database import get_db
|
||||||
from ..models.shareplay import SharePlayRoom, SharePlayCue as SharePlayCueModel
|
from ..schemas.shareplay import SharePlayCueResponse
|
||||||
from ..schemas.shareplay import SharePlayRoomResponse, SharePlayCommand, SharePlayCueResponse
|
|
||||||
from ..services.shareplay import SharePlayManager
|
from ..services.shareplay import SharePlayManager
|
||||||
|
|
||||||
|
|
||||||
@ -112,7 +111,7 @@ async def websocket_endpoint(websocket: WebSocket, room_id: str): # pragma: no
|
|||||||
"type": "playback_update",
|
"type": "playback_update",
|
||||||
"data": {"is_playing": True}
|
"data": {"is_playing": True}
|
||||||
})
|
})
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
elif state and cmd.get("type") == "pause":
|
elif state and cmd.get("type") == "pause":
|
||||||
manager.update_state(room_id, is_playing=False)
|
manager.update_state(room_id, is_playing=False)
|
||||||
@ -122,7 +121,7 @@ async def websocket_endpoint(websocket: WebSocket, room_id: str): # pragma: no
|
|||||||
"type": "playback_update",
|
"type": "playback_update",
|
||||||
"data": {"is_playing": False}
|
"data": {"is_playing": False}
|
||||||
})
|
})
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await websocket.send_json({"type": "ack", "data": message})
|
await websocket.send_json({"type": "ack", "data": message})
|
||||||
@ -136,7 +135,7 @@ async def websocket_endpoint(websocket: WebSocket, room_id: str): # pragma: no
|
|||||||
"type": "chat",
|
"type": "chat",
|
||||||
"data": chat_msg
|
"data": chat_msg
|
||||||
})
|
})
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif message.get("type") == "seek":
|
elif message.get("type") == "seek":
|
||||||
@ -148,7 +147,7 @@ async def websocket_endpoint(websocket: WebSocket, room_id: str): # pragma: no
|
|||||||
"type": "seek_update",
|
"type": "seek_update",
|
||||||
"data": {"position": pos}
|
"data": {"position": pos}
|
||||||
})
|
})
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
|
|||||||
@ -9,4 +9,32 @@ from .account import AccountStatsResponse, ListeningHistoryItem
|
|||||||
from .releases import NewReleaseResponse, ReleaseAlbumResponse
|
from .releases import NewReleaseResponse, ReleaseAlbumResponse
|
||||||
from .events import ConcertEventResponse
|
from .events import ConcertEventResponse
|
||||||
from .search import SearchResultResponse
|
from .search import SearchResultResponse
|
||||||
from .common import PaginatedResponse
|
from .common import PaginatedResponse
|
||||||
|
__all__ = [
|
||||||
|
"SongBase",
|
||||||
|
"SongCreate",
|
||||||
|
"SongResponse",
|
||||||
|
"ScanResult",
|
||||||
|
"PlaylistBase",
|
||||||
|
"PlaylistCreate",
|
||||||
|
"PlaylistResponse",
|
||||||
|
"PlaylistWithSongs",
|
||||||
|
"MoodCategoryResponse",
|
||||||
|
"MoodAnalysisResponse",
|
||||||
|
"MoodPlaylistResponse",
|
||||||
|
"RadioStationResponse",
|
||||||
|
"RadioCurrentResponse",
|
||||||
|
"LofiChannelResponse",
|
||||||
|
"SharePlayRoomResponse",
|
||||||
|
"SharePlayCommand",
|
||||||
|
"SharePlayCueResponse",
|
||||||
|
"UserSettingsResponse",
|
||||||
|
"ServerConfigResponse",
|
||||||
|
"AccountStatsResponse",
|
||||||
|
"ListeningHistoryItem",
|
||||||
|
"NewReleaseResponse",
|
||||||
|
"ReleaseAlbumResponse",
|
||||||
|
"ConcertEventResponse",
|
||||||
|
"SearchResultResponse",
|
||||||
|
"PaginatedResponse",
|
||||||
|
]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from typing import Optional, List, Dict
|
from typing import List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from .song import SongResponse
|
from .song import SongResponse
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from ..models.song import Song
|
from ..models.song import Song
|
||||||
@ -21,7 +20,6 @@ def extract_metadata(file_path: str) -> Dict[str, Any]: # pragma: no cover
|
|||||||
from mutagen.oggvorbis import OggVorbis
|
from mutagen.oggvorbis import OggVorbis
|
||||||
from mutagen.wave import WAVE
|
from mutagen.wave import WAVE
|
||||||
from mutagen.mp4 import MP4
|
from mutagen.mp4 import MP4
|
||||||
from mutagen.id3 import ID3
|
|
||||||
|
|
||||||
metadata: Dict[str, Any] = {}
|
metadata: Dict[str, Any] = {}
|
||||||
ext = os.path.splitext(file_path)[1].lower()
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from ..models.song import Song
|
from ..models.song import Song
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
"""Tests for deep coverage of services and middleware."""
|
"""Tests for deep coverage of services and middleware."""
|
||||||
from unittest.mock import MagicMock, patch, AsyncMock
|
from unittest.mock import MagicMock, patch
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Comprehensive backend endpoint tests for music-app."""
|
"""Comprehensive backend endpoint tests for music-app."""
|
||||||
import json
|
|
||||||
import httpx
|
import httpx
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
import signal
|
|
||||||
|
|
||||||
BASE_URL = "http://localhost:8000"
|
BASE_URL = "http://localhost:8000"
|
||||||
passed = 0
|
passed = 0
|
||||||
@ -25,13 +23,14 @@ def test(name, condition, detail=""):
|
|||||||
|
|
||||||
def start_server():
|
def start_server():
|
||||||
"""Start the backend server."""
|
"""Start the backend server."""
|
||||||
os.chdir("/home/user/playground/music-app/backend")
|
# backend/ directory (parent of this tests/ dir)
|
||||||
|
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
# Remove old DB
|
# Remove old DB
|
||||||
if os.path.exists("app.db"):
|
if os.path.exists("app.db"):
|
||||||
os.remove("app.db")
|
os.remove("app.db")
|
||||||
|
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"],
|
[sys.executable, "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
@ -285,7 +284,7 @@ def main():
|
|||||||
print(f"\n📊 Results: {passed}/{total} passed, {failed} failed")
|
print(f"\n📊 Results: {passed}/{total} passed, {failed} failed")
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
print(f"\n❌ Failed tests:")
|
print("\n❌ Failed tests:")
|
||||||
for e in errors:
|
for e in errors:
|
||||||
print(f" - {e}")
|
print(f" - {e}")
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
"""Router tests - LoFi endpoints."""
|
"""Router tests - LoFi endpoints."""
|
||||||
from app.models.lofi import LofiChannel
|
|
||||||
from app.db.database import SessionLocal
|
|
||||||
|
|
||||||
|
|
||||||
class TestListChannels:
|
class TestListChannels:
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
"""Router tests - SharePlay endpoints."""
|
"""Router tests - SharePlay endpoints."""
|
||||||
from app.models.song import Song
|
|
||||||
from app.models.shareplay import SharePlayRoom
|
|
||||||
from app.db.database import SessionLocal
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateRoom:
|
class TestCreateRoom:
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
"""Extended mood engine and SharePlay service tests."""
|
"""Extended mood engine and SharePlay service tests."""
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
|
|
||||||
class TestAnalyzeLyrics:
|
class TestAnalyzeLyrics:
|
||||||
@ -11,7 +9,7 @@ class TestAnalyzeLyrics:
|
|||||||
assert len(scores) == len(MOOD_NAMES)
|
assert len(scores) == len(MOOD_NAMES)
|
||||||
|
|
||||||
def test_none_input(self):
|
def test_none_input(self):
|
||||||
from app.services.mood_engine import analyze_lyrics, MOOD_NAMES
|
from app.services.mood_engine import analyze_lyrics
|
||||||
scores = analyze_lyrics(None)
|
scores = analyze_lyrics(None)
|
||||||
assert all(v == 0.0 for v in scores.values())
|
assert all(v == 0.0 for v in scores.values())
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,6 @@ class TestTranscode:
|
|||||||
class TestScanDirectory:
|
class TestScanDirectory:
|
||||||
def test_empty_dir(self):
|
def test_empty_dir(self):
|
||||||
from app.services.audio import scan_directory
|
from app.services.audio import scan_directory
|
||||||
from unittest.mock import MagicMock
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
mock_db = MagicMock()
|
mock_db = MagicMock()
|
||||||
mock_db.query.return_value.first.return_value = None
|
mock_db.query.return_value.first.return_value = None
|
||||||
@ -42,7 +41,6 @@ class TestScanDirectory:
|
|||||||
|
|
||||||
def test_skips_non_audio(self):
|
def test_skips_non_audio(self):
|
||||||
from app.services.audio import scan_directory
|
from app.services.audio import scan_directory
|
||||||
from unittest.mock import MagicMock
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
with open(os.path.join(tmpdir, "readme.txt"), "w") as f:
|
with open(os.path.join(tmpdir, "readme.txt"), "w") as f:
|
||||||
f.write("test")
|
f.write("test")
|
||||||
@ -55,7 +53,6 @@ class TestScanDirectory:
|
|||||||
class TestDeleteSong:
|
class TestDeleteSong:
|
||||||
def test_delete_files(self):
|
def test_delete_files(self):
|
||||||
from app.services.audio import delete_song
|
from app.services.audio import delete_song
|
||||||
from unittest.mock import MagicMock
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
tmpdir = tempfile.mkdtemp()
|
||||||
f1 = os.path.join(tmpdir, "t1.ogg")
|
f1 = os.path.join(tmpdir, "t1.ogg")
|
||||||
f2 = os.path.join(tmpdir, "t2.mp3")
|
f2 = os.path.join(tmpdir, "t2.mp3")
|
||||||
@ -73,7 +70,6 @@ class TestDeleteSong:
|
|||||||
|
|
||||||
def test_delete_no_paths(self):
|
def test_delete_no_paths(self):
|
||||||
from app.services.audio import delete_song
|
from app.services.audio import delete_song
|
||||||
from unittest.mock import MagicMock
|
|
||||||
song = MagicMock()
|
song = MagicMock()
|
||||||
song.file_path = None
|
song.file_path = None
|
||||||
song.transcoded_path = None
|
song.transcoded_path = None
|
||||||
@ -84,7 +80,6 @@ class TestDeleteSong:
|
|||||||
class TestGetStreamPath:
|
class TestGetStreamPath:
|
||||||
def test_transcoded_priority(self):
|
def test_transcoded_priority(self):
|
||||||
from app.services.audio import get_stream_path
|
from app.services.audio import get_stream_path
|
||||||
from unittest.mock import MagicMock
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
tmpdir = tempfile.mkdtemp()
|
||||||
f1 = os.path.join(tmpdir, "t.ogg")
|
f1 = os.path.join(tmpdir, "t.ogg")
|
||||||
f2 = os.path.join(tmpdir, "t.mp3")
|
f2 = os.path.join(tmpdir, "t.mp3")
|
||||||
@ -99,7 +94,6 @@ class TestGetStreamPath:
|
|||||||
|
|
||||||
def test_fallback_original(self):
|
def test_fallback_original(self):
|
||||||
from app.services.audio import get_stream_path
|
from app.services.audio import get_stream_path
|
||||||
from unittest.mock import MagicMock
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
tmpdir = tempfile.mkdtemp()
|
||||||
f = os.path.join(tmpdir, "t.mp3")
|
f = os.path.join(tmpdir, "t.mp3")
|
||||||
open(f, "w").close()
|
open(f, "w").close()
|
||||||
@ -112,7 +106,6 @@ class TestGetStreamPath:
|
|||||||
|
|
||||||
def test_no_files(self):
|
def test_no_files(self):
|
||||||
from app.services.audio import get_stream_path
|
from app.services.audio import get_stream_path
|
||||||
from unittest.mock import MagicMock
|
|
||||||
song = MagicMock()
|
song = MagicMock()
|
||||||
song.transcoded_path = None
|
song.transcoded_path = None
|
||||||
song.file_path = None
|
song.file_path = None
|
||||||
|
|||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Internet Radio Page >> shows internet radio title
|
|
||||||
- Location: e2e/features.spec.ts:74:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/radio
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/radio", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 71 | await page.goto('/radio');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/radio
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Internet Radio Page >> shows search bar
|
|
||||||
- Location: e2e/features.spec.ts:78:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/radio
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/radio", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 71 | await page.goto('/radio');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/radio
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Internet Radio Page >> shows stations section
|
|
||||||
- Location: e2e/features.spec.ts:83:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/radio
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/radio", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 71 | await page.goto('/radio');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/radio
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> LoFi Channel Page >> shows LoFi label on channels
|
|
||||||
- Location: e2e/features.spec.ts:55:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/lofi
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/lofi", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 42 | await page.goto('/lofi');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/lofi
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> LoFi Channel Page >> shows lofi channel title
|
|
||||||
- Location: e2e/features.spec.ts:45:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/lofi
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/lofi", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 42 | await page.goto('/lofi');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/lofi
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> LoFi Channel Page >> shows lofi channels
|
|
||||||
- Location: e2e/features.spec.ts:49:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/lofi
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/lofi", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 42 | await page.goto('/lofi');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/lofi
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Mood Radio Page >> clicking set the mood changes mood
|
|
||||||
- Location: e2e/features.spec.ts:25:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/mood", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 5 | await page.goto('/mood');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
```
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Mood Radio Page >> shows circular progress indicator
|
|
||||||
- Location: e2e/features.spec.ts:33:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/mood", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 5 | await page.goto('/mood');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
```
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Mood Radio Page >> shows mood name
|
|
||||||
- Location: e2e/features.spec.ts:12:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/mood", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 5 | await page.goto('/mood');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
```
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Mood Radio Page >> shows mood radio title
|
|
||||||
- Location: e2e/features.spec.ts:8:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/mood", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 5 | await page.goto('/mood');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
```
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Mood Radio Page >> shows play mood button
|
|
||||||
- Location: e2e/features.spec.ts:17:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/mood", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 5 | await page.goto('/mood');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
```
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> Mood Radio Page >> shows set the mood button
|
|
||||||
- Location: e2e/features.spec.ts:21:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/mood", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 5 | await page.goto('/mood');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/mood
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> New Releases Page >> shows empty state or releases
|
|
||||||
- Location: e2e/features.spec.ts:97:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/releases
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/releases", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 90 | await page.goto('/releases');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/releases
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> New Releases Page >> shows new releases title
|
|
||||||
- Location: e2e/features.spec.ts:93:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/releases
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/releases", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
1 | import { test, expect } from '@playwright/test';
|
|
||||||
2 |
|
|
||||||
3 | test.describe('Mood Radio Page', () => {
|
|
||||||
4 | test.beforeEach(async ({ page }) => {
|
|
||||||
5 | await page.goto('/mood');
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 90 | await page.goto('/releases');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/releases
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
106 | await page.goto('/shareplay');
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> SharePlay Page >> shows create room option
|
|
||||||
- Location: e2e/features.spec.ts:113:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/shareplay
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/shareplay", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 106 | await page.goto('/shareplay');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/shareplay
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
# Instructions
|
|
||||||
|
|
||||||
- Following Playwright test failed.
|
|
||||||
- Explain why, be concise, respect Playwright best practices.
|
|
||||||
- Provide a snippet of code with the fix, if possible.
|
|
||||||
|
|
||||||
# Test info
|
|
||||||
|
|
||||||
- Name: features.spec.ts >> SharePlay Page >> shows shareplay title
|
|
||||||
- Location: e2e/features.spec.ts:109:7
|
|
||||||
|
|
||||||
# Error details
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/shareplay
|
|
||||||
Call log:
|
|
||||||
- navigating to "http://localhost:5173/shareplay", waiting until "load"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Test source
|
|
||||||
|
|
||||||
```ts
|
|
||||||
6 | });
|
|
||||||
7 |
|
|
||||||
8 | test('shows mood radio title', async ({ page }) => {
|
|
||||||
9 | await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
||||||
10 | });
|
|
||||||
11 |
|
|
||||||
12 | test('shows mood name', async ({ page }) => {
|
|
||||||
13 | // Should show one of the mood names
|
|
||||||
14 | await expect(page.getByText(/Sad|Happy|Energetic|Focused|Chill/)).toBeVisible();
|
|
||||||
15 | });
|
|
||||||
16 |
|
|
||||||
17 | test('shows play mood button', async ({ page }) => {
|
|
||||||
18 | await expect(page.getByText(/Play Mood|Playing/)).toBeVisible();
|
|
||||||
19 | });
|
|
||||||
20 |
|
|
||||||
21 | test('shows set the mood button', async ({ page }) => {
|
|
||||||
22 | await expect(page.getByText('Set the Mood')).toBeVisible();
|
|
||||||
23 | });
|
|
||||||
24 |
|
|
||||||
25 | test('clicking set the mood changes mood', async ({ page }) => {
|
|
||||||
26 | const currentMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
27 | await page.getByText('Set the Mood').click();
|
|
||||||
28 | await page.waitForTimeout(500);
|
|
||||||
29 | const newMood = await page.getByRole('heading', { level: 2 }).textContent();
|
|
||||||
30 | // Mood should change (might rarely be same by random chance)
|
|
||||||
31 | });
|
|
||||||
32 |
|
|
||||||
33 | test('shows circular progress indicator', async ({ page }) => {
|
|
||||||
34 | // SVG circle should be present
|
|
||||||
35 | const circle = page.locator('svg circle').last();
|
|
||||||
36 | await expect(circle).toBeVisible();
|
|
||||||
37 | });
|
|
||||||
38 | });
|
|
||||||
39 |
|
|
||||||
40 | test.describe('LoFi Channel Page', () => {
|
|
||||||
41 | test.beforeEach(async ({ page }) => {
|
|
||||||
42 | await page.goto('/lofi');
|
|
||||||
43 | });
|
|
||||||
44 |
|
|
||||||
45 | test('shows lofi channel title', async ({ page }) => {
|
|
||||||
46 | await expect(page.getByText('LoFi Channel')).toBeVisible();
|
|
||||||
47 | });
|
|
||||||
48 |
|
|
||||||
49 | test('shows lofi channels', async ({ page }) => {
|
|
||||||
50 | // Should show at least 3 channels
|
|
||||||
51 | const channels = page.locator('[class*="aspect-video"]');
|
|
||||||
52 | await expect(channels).toHaveCount(atLeast(3));
|
|
||||||
53 | });
|
|
||||||
54 |
|
|
||||||
55 | test('shows LoFi label on channels', async ({ page }) => {
|
|
||||||
56 | await expect(page.getByText('LoFi')).toBeVisible();
|
|
||||||
57 | });
|
|
||||||
58 |
|
|
||||||
59 | function atLeast(n: number) {
|
|
||||||
60 | return {
|
|
||||||
61 | pass(received: number) {
|
|
||||||
62 | return received >= n;
|
|
||||||
63 | },
|
|
||||||
64 | message: () => `expected at least ${n} elements`,
|
|
||||||
65 | };
|
|
||||||
66 | }
|
|
||||||
67 | });
|
|
||||||
68 |
|
|
||||||
69 | test.describe('Internet Radio Page', () => {
|
|
||||||
70 | test.beforeEach(async ({ page }) => {
|
|
||||||
71 | await page.goto('/radio');
|
|
||||||
72 | });
|
|
||||||
73 |
|
|
||||||
74 | test('shows internet radio title', async ({ page }) => {
|
|
||||||
75 | await expect(page.getByText('Internet Radio')).toBeVisible();
|
|
||||||
76 | });
|
|
||||||
77 |
|
|
||||||
78 | test('shows search bar', async ({ page }) => {
|
|
||||||
79 | const searchInput = page.getByPlaceholder(/What music is calling/);
|
|
||||||
80 | await expect(searchInput).toBeVisible();
|
|
||||||
81 | });
|
|
||||||
82 |
|
|
||||||
83 | test('shows stations section', async ({ page }) => {
|
|
||||||
84 | await expect(page.getByText('Stations')).toBeVisible();
|
|
||||||
85 | });
|
|
||||||
86 | });
|
|
||||||
87 |
|
|
||||||
88 | test.describe('New Releases Page', () => {
|
|
||||||
89 | test.beforeEach(async ({ page }) => {
|
|
||||||
90 | await page.goto('/releases');
|
|
||||||
91 | });
|
|
||||||
92 |
|
|
||||||
93 | test('shows new releases title', async ({ page }) => {
|
|
||||||
94 | await expect(page.getByText('New Releases')).toBeVisible();
|
|
||||||
95 | });
|
|
||||||
96 |
|
|
||||||
97 | test('shows empty state or releases', async ({ page }) => {
|
|
||||||
98 | // Either shows releases or empty state with "Add music" message
|
|
||||||
99 | const hasContent = await page.locator('h2.font-display').count();
|
|
||||||
100 | expect(hasContent >= 0).toBeTruthy();
|
|
||||||
101 | });
|
|
||||||
102 | });
|
|
||||||
103 |
|
|
||||||
104 | test.describe('SharePlay Page', () => {
|
|
||||||
105 | test.beforeEach(async ({ page }) => {
|
|
||||||
> 106 | await page.goto('/shareplay');
|
|
||||||
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:5173/shareplay
|
|
||||||
107 | });
|
|
||||||
108 |
|
|
||||||
109 | test('shows shareplay title', async ({ page }) => {
|
|
||||||
110 | await expect(page.getByText('SharePlay')).toBeVisible();
|
|
||||||
111 | });
|
|
||||||
112 |
|
|
||||||
113 | test('shows create room option', async ({ page }) => {
|
|
||||||
114 | await expect(page.getByText(/Create Room|Start a SharePlay/)).toBeVisible();
|
|
||||||
115 | });
|
|
||||||
116 |
|
|
||||||
117 | test('can create a room', async ({ page }) => {
|
|
||||||
118 | await page.getByRole('button', { name: /Create Room/ }).click();
|
|
||||||
119 | await page.waitForTimeout(500);
|
|
||||||
120 | // Should show room UI
|
|
||||||
121 | await expect(page.getByText(/Currently Playing|Add Song to Cue/)).toBeVisible();
|
|
||||||
122 | });
|
|
||||||
123 | });
|
|
||||||
124 |
|
|
||||||
125 | test.describe('Playlist Page', () => {
|
|
||||||
126 | test('shows 404 for non-existent playlist', async ({ page }) => {
|
|
||||||
127 | await page.goto('/playlist/nonexistent-id');
|
|
||||||
128 | await expect(page.getByText(/not found|back to library/i)).toBeVisible();
|
|
||||||
129 | });
|
|
||||||
130 | });
|
|
||||||
```
|
|
||||||
Loading…
x
Reference in New Issue
Block a user