From 8a073bae4a345e0fcc02fffe34efdce82c95d580 Mon Sep 17 00:00:00 2001 From: Felix Evers Date: Sun, 21 Dec 2025 00:50:57 +0100 Subject: [PATCH 01/45] add tests --- .github/workflows/backend-tests.yml | 67 ++++ .github/workflows/e2e-tests.yml | 100 ++++++ .github/workflows/frontend-tests.yml | 31 ++ .github/workflows/tests.yml | 174 +++++++++ backend/api/resolvers/base.py | 101 ++++++ backend/api/resolvers/patient.py | 331 ++++-------------- backend/api/resolvers/property.py | 39 +-- backend/api/resolvers/task.py | 249 +++++-------- backend/api/resolvers/utils.py | 36 -- backend/api/services/__init__.py | 0 backend/api/services/base.py | 54 +++ backend/api/services/checksum.py | 21 ++ backend/api/services/datetime.py | 11 + backend/api/services/location.py | 81 +++++ backend/api/services/notifications.py | 28 ++ backend/api/services/property.py | 43 +++ backend/api/services/subscription.py | 21 ++ backend/api/services/validation.py | 31 ++ backend/pytest.ini | 8 + backend/requirements.txt | 4 + backend/tests/__init__.py | 0 backend/tests/conftest.py | 94 +++++ backend/tests/integration/__init__.py | 0 .../integration/test_patient_resolver.py | 77 ++++ .../tests/integration/test_task_resolver.py | 91 +++++ backend/tests/unit/__init__.py | 0 backend/tests/unit/test_base_repository.py | 73 ++++ backend/tests/unit/test_base_resolvers.py | 58 +++ backend/tests/unit/test_checksum.py | 24 ++ backend/tests/unit/test_location_service.py | 100 ++++++ backend/tests/unit/test_property_service.py | 88 +++++ tests/__init__.py | 0 tests/e2e/__init__.py | 0 tests/e2e/auth.spec.ts | 56 +++ tests/e2e/navigation.spec.ts | 40 +++ tests/e2e/playwright.config.ts | 27 ++ tests/e2e/tasks.spec.ts | 48 +++ web/package.json | 1 + 38 files changed, 1719 insertions(+), 488 deletions(-) create mode 100644 .github/workflows/backend-tests.yml create mode 100644 .github/workflows/e2e-tests.yml create mode 100644 .github/workflows/frontend-tests.yml create mode 100644 .github/workflows/tests.yml create mode 100644 backend/api/resolvers/base.py delete mode 100644 backend/api/resolvers/utils.py create mode 100644 backend/api/services/__init__.py create mode 100644 backend/api/services/base.py create mode 100644 backend/api/services/checksum.py create mode 100644 backend/api/services/datetime.py create mode 100644 backend/api/services/location.py create mode 100644 backend/api/services/notifications.py create mode 100644 backend/api/services/property.py create mode 100644 backend/api/services/subscription.py create mode 100644 backend/api/services/validation.py create mode 100644 backend/pytest.ini create mode 100644 backend/tests/__init__.py create mode 100644 backend/tests/conftest.py create mode 100644 backend/tests/integration/__init__.py create mode 100644 backend/tests/integration/test_patient_resolver.py create mode 100644 backend/tests/integration/test_task_resolver.py create mode 100644 backend/tests/unit/__init__.py create mode 100644 backend/tests/unit/test_base_repository.py create mode 100644 backend/tests/unit/test_base_resolvers.py create mode 100644 backend/tests/unit/test_checksum.py create mode 100644 backend/tests/unit/test_location_service.py create mode 100644 backend/tests/unit/test_property_service.py create mode 100644 tests/__init__.py create mode 100644 tests/e2e/__init__.py create mode 100644 tests/e2e/auth.spec.ts create mode 100644 tests/e2e/navigation.spec.ts create mode 100644 tests/e2e/playwright.config.ts create mode 100644 tests/e2e/tasks.spec.ts diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml new file mode 100644 index 00000000..786d2e2c --- /dev/null +++ b/.github/workflows/backend-tests.yml @@ -0,0 +1,67 @@ +name: Backend Tests + +on: + push: + branches: [main, develop] + paths: + - 'backend/**' + - '.github/workflows/backend-tests.yml' + pull_request: + branches: [main, develop] + paths: + - 'backend/**' + - '.github/workflows/backend-tests.yml' + +jobs: + test: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + redis: + image: redis:8 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install dependencies + working-directory: backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run unit tests + working-directory: backend + run: | + pytest tests/unit -v --cov=api --cov-report=xml + env: + DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres + REDIS_URL: redis://localhost:6379 + - name: Run integration tests + working-directory: backend + run: | + pytest tests/integration -v + env: + DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres + REDIS_URL: redis://localhost:6379 + + diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 00000000..6c09bf55 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,100 @@ +name: E2E Tests + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + workflow_dispatch: + +jobs: + e2e: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + redis: + image: redis:8 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install backend dependencies + working-directory: backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Install frontend dependencies + working-directory: web + run: npm ci + - name: Install Playwright + run: npx playwright install --with-deps + - name: Run database migrations + working-directory: backend + run: | + alembic upgrade head + env: + DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres + - name: Start backend + working-directory: backend + run: | + uvicorn main:app --host 0.0.0.0 --port 8000 & + env: + DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres + REDIS_URL: redis://localhost:6379 + ISSUER_URI: http://localhost:8080/realms/tasks + PUBLIC_ISSUER_URI: http://localhost:8080/realms/tasks + CLIENT_ID: tasks-backend + CLIENT_SECRET: tasks-secret + ENV: test + - name: Build frontend + working-directory: web + run: npm run build + - name: Start frontend + working-directory: web + run: | + npm start & + env: + NEXT_PUBLIC_API_URL: http://localhost:8000/graphql + - name: Wait for services + run: | + timeout 60 bash -c 'until curl -f http://localhost:8000/graphql; do sleep 2; done' + timeout 60 bash -c 'until curl -f http://localhost:3000; do sleep 2; done' + - name: Run E2E tests + run: | + npx playwright test + env: + E2E_BASE_URL: http://localhost:3000 + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml new file mode 100644 index 00000000..8788a47f --- /dev/null +++ b/.github/workflows/frontend-tests.yml @@ -0,0 +1,31 @@ +name: Frontend Tests + +on: + push: + branches: [main, develop] + paths: + - 'web/**' + - '.github/workflows/frontend-tests.yml' + pull_request: + branches: [main, develop] + paths: + - 'web/**' + - '.github/workflows/frontend-tests.yml' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + working-directory: web + run: npm ci + - name: Run linter + working-directory: web + run: npm run lint + + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..a19bfac4 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,174 @@ +name: Tests + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + backend-tests: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13"] + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('backend/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + working-directory: backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run unit tests + working-directory: backend + run: | + pytest tests/unit -v --cov=api --cov=database --cov-report=xml --cov-report=term + + - name: Run integration tests + working-directory: backend + run: | + pytest tests/integration -v + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./backend/coverage.xml + flags: backend + name: backend-coverage + + frontend-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: web/package-lock.json + + - name: Install dependencies + working-directory: web + run: npm ci + + - name: Run linter + working-directory: web + run: npm run lint || true + + - name: Run type check + working-directory: web + run: npm run type-check || true + + e2e-tests: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: web/package-lock.json + + - name: Install backend dependencies + working-directory: backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install frontend dependencies + working-directory: web + run: npm ci + + - name: Install Playwright browsers + working-directory: tests + run: npx playwright install --with-deps chromium + + - name: Run E2E tests + working-directory: tests + env: + E2E_BASE_URL: http://localhost:3000 + run: npx playwright test + + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: tests/playwright-report/ + retention-days: 30 diff --git a/backend/api/resolvers/base.py b/backend/api/resolvers/base.py new file mode 100644 index 00000000..1fb8102e --- /dev/null +++ b/backend/api/resolvers/base.py @@ -0,0 +1,101 @@ +from collections.abc import AsyncGenerator +from typing import Generic, TypeVar + +import strawberry +from api.context import Info +from api.services.base import BaseRepository +from api.services.notifications import notify_entity_created, notify_entity_deleted, notify_entity_update +from api.services.subscription import create_redis_subscription +from sqlalchemy.ext.asyncio import AsyncSession + +ModelType = TypeVar("ModelType") + + +class BaseQueryResolver(Generic[ModelType]): + def __init__(self, model: type[ModelType]): + self.model = model + + def get_repository(self, db: AsyncSession) -> BaseRepository[ModelType]: + return BaseRepository(db, self.model) + + @strawberry.field + async def get_by_id(self, info: Info, id: strawberry.ID) -> ModelType | None: + repo = self.get_repository(info.context.db) + return await repo.get_by_id(id) + + @strawberry.field + async def get_all(self, info: Info) -> list[ModelType]: + repo = self.get_repository(info.context.db) + return await repo.get_all() + + +class BaseMutationResolver(Generic[ModelType]): + def __init__(self, model: type[ModelType], entity_name: str): + self.model = model + self.entity_name = entity_name + + def get_repository(self, db: AsyncSession) -> BaseRepository[ModelType]: + return BaseRepository(db, self.model) + + async def delete_entity( + self, + info: Info, + entity: ModelType, + related_entity_type: str | None = None, + related_entity_id: str | None = None, + ) -> None: + repo = self.get_repository(info.context.db) + entity_id = entity.id + await repo.delete(entity) + await notify_entity_deleted(self.entity_name, entity_id, related_entity_type, related_entity_id) + + async def create_and_notify( + self, + info: Info, + entity: ModelType, + related_entity_type: str | None = None, + related_entity_id: str | None = None, + ) -> ModelType: + repo = self.get_repository(info.context.db) + await repo.create(entity) + await notify_entity_created(self.entity_name, entity.id) + if related_entity_type and related_entity_id: + await notify_entity_update(related_entity_type, related_entity_id) + return entity + + async def update_and_notify( + self, + info: Info, + entity: ModelType, + related_entity_type: str | None = None, + related_entity_id: str | None = None, + ) -> ModelType: + repo = self.get_repository(info.context.db) + await repo.update(entity) + await notify_entity_update(self.entity_name, entity.id, related_entity_type, related_entity_id) + return entity + + +class BaseSubscriptionResolver: + def __init__(self, entity_name: str): + self.entity_name = entity_name + + @strawberry.subscription + async def entity_created(self, info: Info) -> AsyncGenerator[strawberry.ID, None]: + async for entity_id in create_redis_subscription(f"{self.entity_name}_created"): + yield entity_id + + @strawberry.subscription + async def entity_updated( + self, + info: Info, + entity_id: strawberry.ID | None = None, + ) -> AsyncGenerator[strawberry.ID, None]: + async for updated_id in create_redis_subscription(f"{self.entity_name}_updated", entity_id): + yield updated_id + + @strawberry.subscription + async def entity_deleted(self, info: Info) -> AsyncGenerator[strawberry.ID, None]: + async for entity_id in create_redis_subscription(f"{self.entity_name}_deleted"): + yield entity_id + diff --git a/backend/api/resolvers/patient.py b/backend/api/resolvers/patient.py index dc9985cc..31d007f3 100644 --- a/backend/api/resolvers/patient.py +++ b/backend/api/resolvers/patient.py @@ -4,43 +4,16 @@ from api.audit import audit_log from api.context import Info from api.inputs import CreatePatientInput, PatientState, Sex, UpdatePatientInput +from api.resolvers.base import BaseMutationResolver, BaseSubscriptionResolver +from api.services.base import BaseRepository +from api.services.checksum import validate_checksum +from api.services.location import LocationService +from api.services.property import PropertyService from api.types.patient import PatientType from database import models -from database.session import publish_to_redis, redis_client from sqlalchemy import select from sqlalchemy.orm import aliased, selectinload -from .utils import process_properties - - -def validate_location_kind(location: models.LocationNode, expected_kind: str, field_name: str) -> None: - """Validate that a location has the expected kind.""" - if location.kind.upper() != expected_kind.upper(): - raise Exception( - f"{field_name} must be a location of kind {expected_kind}, " - f"but got {location.kind}" - ) - - -def validate_position_kind(location: models.LocationNode, field_name: str) -> None: - """Validate that a location is a valid position type.""" - allowed_kinds = {"HOSPITAL", "PRACTICE", "CLINIC", "WARD", "BED", "ROOM"} - if location.kind.upper() not in allowed_kinds: - raise Exception( - f"{field_name} must be a location of kind HOSPITAL, PRACTICE, CLINIC, " - f"WARD, BED, or ROOM, but got {location.kind}" - ) - - -def validate_team_kind(location: models.LocationNode, field_name: str) -> None: - """Validate that a location is a valid team type.""" - allowed_kinds = {"CLINIC", "TEAM", "PRACTICE", "HOSPITAL"} - if location.kind.upper() not in allowed_kinds: - raise Exception( - f"{field_name} must be a location of kind CLINIC, TEAM, PRACTICE, " - f"or HOSPITAL, but got {location.kind}" - ) - @strawberry.type class PatientQuery: @@ -137,7 +110,16 @@ async def recent_patients( @strawberry.type -class PatientMutation: +class PatientMutation(BaseMutationResolver[models.Patient]): + def __init__(self): + super().__init__(models.Patient, "patient") + + def _get_property_service(self, db) -> PropertyService: + return PropertyService(db) + + def _get_location_service(self, db) -> LocationService: + return LocationService(db) + @strawberry.mutation @audit_log("create_patient") async def create_patient( @@ -146,44 +128,18 @@ async def create_patient( data: CreatePatientInput, ) -> PatientType: db = info.context.db + location_service = self._get_location_service(db) initial_state = data.state.value if data.state else PatientState.WAIT.value - clinic_result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id == data.clinic_id, - ), - ) - clinic = clinic_result.scalars().first() - if not clinic: - raise Exception(f"Clinic location with id {data.clinic_id} not found") - validate_location_kind(clinic, "CLINIC", "clinic_id") + await location_service.validate_and_get_clinic(data.clinic_id) position = None if data.position_id: - position_result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id == data.position_id, - ), - ) - position = position_result.scalars().first() - if not position: - raise Exception(f"Position location with id {data.position_id} not found") - validate_position_kind(position, "position_id") + position = await location_service.validate_and_get_position(data.position_id) teams = [] if data.team_ids: - teams_result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id.in_(data.team_ids), - ), - ) - teams = list(teams_result.scalars().all()) - if len(teams) != len(data.team_ids): - found_ids = {t.id for t in teams} - missing_ids = set(data.team_ids) - found_ids - raise Exception(f"Team locations with ids {missing_ids} not found") - for team in teams: - validate_team_kind(team, "team_ids") + teams = await location_service.validate_and_get_teams(data.team_ids) new_patient = models.Patient( firstname=data.firstname, @@ -200,36 +156,20 @@ async def create_patient( new_patient.teams = teams if data.assigned_location_ids: - result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id.in_(data.assigned_location_ids), - ), - ) - locations = result.scalars().all() - new_patient.assigned_locations = list(locations) + locations = await location_service.get_locations_by_ids(data.assigned_location_ids) + new_patient.assigned_locations = locations elif data.assigned_location_id: - result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id == data.assigned_location_id, - ), - ) - location = result.scalars().first() - if location: - new_patient.assigned_locations = [location] + location = await location_service.get_location_by_id(data.assigned_location_id) + new_patient.assigned_locations = [location] if location else [] if data.properties: - await process_properties( - db, - new_patient, - data.properties, - "patient", - ) - - db.add(new_patient) - await db.commit() + property_service = self._get_property_service(db) + await property_service.process_properties(new_patient, data.properties, "patient") + repo = self.get_repository(db) + await repo.create(new_patient) await db.refresh(new_patient, ["assigned_locations", "teams"]) - await publish_to_redis("patient_created", new_patient.id) + await self.create_and_notify(info, new_patient) return new_patient @strawberry.mutation @@ -241,6 +181,7 @@ async def update_patient( data: UpdatePatientInput, ) -> PatientType: db = info.context.db + repo = BaseRepository(db, models.Patient) result = await db.execute( select(models.Patient) .where(models.Patient.id == id) @@ -254,22 +195,7 @@ async def update_patient( raise Exception("Patient not found") if data.checksum: - patient_type = PatientType( - id=patient.id, - firstname=patient.firstname, - lastname=patient.lastname, - birthdate=patient.birthdate, - sex=Sex(patient.sex), - state=PatientState(patient.state), - assigned_location_id=patient.assigned_location_id, - clinic_id=patient.clinic_id, - position_id=patient.position_id, - ) - current_checksum = patient_type.checksum - if data.checksum != current_checksum: - raise Exception( - f"CONFLICT: Patient data has been modified. Expected checksum: {current_checksum}, Got: {data.checksum}" - ) + validate_checksum(patient, data.checksum, "Patient") if data.firstname is not None: patient.firstname = data.firstname @@ -280,96 +206,56 @@ async def update_patient( if data.sex is not None: patient.sex = data.sex.value + location_service = self._get_location_service(db) + if data.clinic_id is not None: - clinic_result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id == data.clinic_id, - ), - ) - clinic = clinic_result.scalars().first() - if not clinic: - raise Exception(f"Clinic location with id {data.clinic_id} not found") - validate_location_kind(clinic, "CLINIC", "clinic_id") + await location_service.validate_and_get_clinic(data.clinic_id) patient.clinic_id = data.clinic_id if data.position_id is not strawberry.UNSET: if data.position_id is None: patient.position_id = None else: - position_result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id == data.position_id, - ), - ) - position = position_result.scalars().first() - if not position: - raise Exception(f"Position location with id {data.position_id} not found") - validate_position_kind(position, "position_id") + await location_service.validate_and_get_position(data.position_id) patient.position_id = data.position_id if data.team_ids is not strawberry.UNSET: if data.team_ids is None or len(data.team_ids) == 0: patient.teams = [] else: - teams_result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id.in_(data.team_ids), - ), - ) - teams = list(teams_result.scalars().all()) - if len(teams) != len(data.team_ids): - found_ids = {t.id for t in teams} - missing_ids = set(data.team_ids) - found_ids - raise Exception(f"Team locations with ids {missing_ids} not found") - for team in teams: - validate_team_kind(team, "team_ids") - patient.teams = teams + patient.teams = await location_service.validate_and_get_teams(data.team_ids) if data.assigned_location_ids is not None: - result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id.in_(data.assigned_location_ids), - ), - ) - locations = result.scalars().all() - patient.assigned_locations = list(locations) + locations = await location_service.get_locations_by_ids(data.assigned_location_ids) + patient.assigned_locations = locations elif data.assigned_location_id is not None: - result = await db.execute( - select(models.LocationNode).where( - models.LocationNode.id == data.assigned_location_id, - ), - ) - location = result.scalars().first() - if location: - patient.assigned_locations = [location] - else: - patient.assigned_locations = [] + location = await location_service.get_location_by_id(data.assigned_location_id) + patient.assigned_locations = [location] if location else [] if data.properties: - await process_properties(db, patient, data.properties, "patient") + property_service = self._get_property_service(db) + await property_service.process_properties(patient, data.properties, "patient") - await db.commit() + await self.update_and_notify(info, patient) await db.refresh(patient, ["assigned_locations", "teams"]) - await publish_to_redis("patient_updated", patient.id) return patient @strawberry.mutation @audit_log("delete_patient") async def delete_patient(self, info: Info, id: strawberry.ID) -> bool: - db = info.context.db - result = await db.execute( - select(models.Patient).where(models.Patient.id == id), - ) - patient = result.scalars().first() + repo = self.get_repository(info.context.db) + patient = await repo.get_by_id(id) if not patient: return False - await db.delete(patient) - await db.commit() + await self.delete_entity(info, patient) return True - @strawberry.mutation - @audit_log("admit_patient") - async def admit_patient(self, info: Info, id: strawberry.ID) -> PatientType: + async def _update_patient_state( + self, + info: Info, + id: strawberry.ID, + state: PatientState, + ) -> PatientType: db = info.context.db result = await db.execute( select(models.Patient) @@ -382,95 +268,43 @@ async def admit_patient(self, info: Info, id: strawberry.ID) -> PatientType: patient = result.scalars().first() if not patient: raise Exception("Patient not found") - patient.state = PatientState.ADMITTED.value - await db.commit() + patient.state = state.value + await self.update_and_notify(info, patient) await db.refresh(patient, ["assigned_locations"]) - await publish_to_redis("patient_updated", patient.id) - await publish_to_redis("patient_state_changed", patient.id) + from api.services.notifications import notify_entity_update + await notify_entity_update("patient_state_changed", patient.id) return patient + @strawberry.mutation + @audit_log("admit_patient") + async def admit_patient(self, info: Info, id: strawberry.ID) -> PatientType: + return await self._update_patient_state(info, id, PatientState.ADMITTED) + @strawberry.mutation @audit_log("discharge_patient") async def discharge_patient(self, info: Info, id: strawberry.ID) -> PatientType: - db = info.context.db - result = await db.execute( - select(models.Patient) - .where(models.Patient.id == id) - .options( - selectinload(models.Patient.assigned_locations), - selectinload(models.Patient.teams), - ), - ) - patient = result.scalars().first() - if not patient: - raise Exception("Patient not found") - patient.state = PatientState.DISCHARGED.value - await db.commit() - await db.refresh(patient, ["assigned_locations"]) - await publish_to_redis("patient_updated", patient.id) - await publish_to_redis("patient_state_changed", patient.id) - return patient + return await self._update_patient_state(info, id, PatientState.DISCHARGED) @strawberry.mutation @audit_log("mark_patient_dead") async def mark_patient_dead(self, info: Info, id: strawberry.ID) -> PatientType: - db = info.context.db - result = await db.execute( - select(models.Patient) - .where(models.Patient.id == id) - .options( - selectinload(models.Patient.assigned_locations), - selectinload(models.Patient.teams), - ), - ) - patient = result.scalars().first() - if not patient: - raise Exception("Patient not found") - patient.state = PatientState.DEAD.value - await db.commit() - await db.refresh(patient, ["assigned_locations"]) - await publish_to_redis("patient_updated", patient.id) - await publish_to_redis("patient_state_changed", patient.id) - return patient + return await self._update_patient_state(info, id, PatientState.DEAD) @strawberry.mutation @audit_log("wait_patient") async def wait_patient(self, info: Info, id: strawberry.ID) -> PatientType: - db = info.context.db - result = await db.execute( - select(models.Patient) - .where(models.Patient.id == id) - .options( - selectinload(models.Patient.assigned_locations), - selectinload(models.Patient.teams), - ), - ) - patient = result.scalars().first() - if not patient: - raise Exception("Patient not found") - patient.state = PatientState.WAIT.value - await db.commit() - await db.refresh(patient, ["assigned_locations"]) - await publish_to_redis("patient_updated", patient.id) - await publish_to_redis("patient_state_changed", patient.id) - return patient + return await self._update_patient_state(info, id, PatientState.WAIT) @strawberry.type -class PatientSubscription: +class PatientSubscription(BaseSubscriptionResolver): + def __init__(self): + super().__init__("patient") + @strawberry.subscription - async def patient_created( - self, - info: Info, - ) -> AsyncGenerator[strawberry.ID, None]: - pubsub = redis_client.pubsub() - await pubsub.subscribe("patient_created") - try: - async for message in pubsub.listen(): - if message["type"] == "message": - yield message["data"] - finally: - await pubsub.close() + async def patient_created(self, info: Info) -> AsyncGenerator[strawberry.ID, None]: + async for patient_id in self.entity_created(info): + yield patient_id @strawberry.subscription async def patient_updated( @@ -478,16 +312,8 @@ async def patient_updated( info: Info, patient_id: strawberry.ID | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - pubsub = redis_client.pubsub() - await pubsub.subscribe("patient_updated") - try: - async for message in pubsub.listen(): - if message["type"] == "message": - patient_id_str = message["data"] - if patient_id is None or patient_id_str == patient_id: - yield patient_id_str - finally: - await pubsub.close() + async for updated_id in self.entity_updated(info, patient_id): + yield updated_id @strawberry.subscription async def patient_state_changed( @@ -495,13 +321,6 @@ async def patient_state_changed( info: Info, patient_id: strawberry.ID | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - pubsub = redis_client.pubsub() - await pubsub.subscribe("patient_state_changed") - try: - async for message in pubsub.listen(): - if message["type"] == "message": - patient_id_str = message["data"] - if patient_id is None or patient_id_str == patient_id: - yield patient_id_str - finally: - await pubsub.close() + from api.services.subscription import create_redis_subscription + async for updated_id in create_redis_subscription("patient_state_changed", patient_id): + yield updated_id diff --git a/backend/api/resolvers/property.py b/backend/api/resolvers/property.py index 12f90b31..5da8a310 100644 --- a/backend/api/resolvers/property.py +++ b/backend/api/resolvers/property.py @@ -4,6 +4,8 @@ CreatePropertyDefinitionInput, UpdatePropertyDefinitionInput, ) +from api.resolvers.base import BaseMutationResolver +from api.services.base import BaseRepository from api.types.property import PropertyDefinitionType from database import models from sqlalchemy import select @@ -23,7 +25,10 @@ async def property_definitions( @strawberry.type -class PropertyDefinitionMutation: +class PropertyDefinitionMutation(BaseMutationResolver[models.PropertyDefinition]): + def __init__(self): + super().__init__(models.PropertyDefinition, "property_definition") + @strawberry.mutation async def create_property_definition( self, @@ -41,10 +46,7 @@ async def create_property_definition( is_active=data.is_active, allowed_entities=entities_str, ) - info.context.db.add(defn) - await info.context.db.commit() - await info.context.db.refresh(defn) - return defn + return await self.create_and_notify(info, defn) @strawberry.mutation async def update_property_definition( @@ -53,15 +55,8 @@ async def update_property_definition( id: strawberry.ID, data: UpdatePropertyDefinitionInput, ) -> PropertyDefinitionType: - db = info.context.db - result = await db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id == id, - ), - ) - defn = result.scalars().first() - if not defn: - raise Exception("Property Definition not found") + repo = self.get_repository(info.context.db) + defn = await repo.get_by_id_or_raise(id, "Property Definition not found") if data.name is not None: defn.name = data.name @@ -76,9 +71,7 @@ async def update_property_definition( [e.value for e in data.allowed_entities], ) - await db.commit() - await db.refresh(defn) - return defn + return await self.update_and_notify(info, defn) @strawberry.mutation async def delete_property_definition( @@ -86,16 +79,10 @@ async def delete_property_definition( info: Info, id: strawberry.ID, ) -> bool: - db = info.context.db - result = await db.execute( - select(models.PropertyDefinition).where( - models.PropertyDefinition.id == id, - ), - ) - defn = result.scalars().first() + repo = self.get_repository(info.context.db) + defn = await repo.get_by_id(id) if not defn: return False - await db.delete(defn) - await db.commit() + await self.delete_entity(info, defn) return True diff --git a/backend/api/resolvers/task.py b/backend/api/resolvers/task.py index fe638e17..102eab6c 100644 --- a/backend/api/resolvers/task.py +++ b/backend/api/resolvers/task.py @@ -1,27 +1,25 @@ from collections.abc import AsyncGenerator -from datetime import timezone - import strawberry from api.audit import audit_log from api.context import Info from api.inputs import CreateTaskInput, UpdateTaskInput +from api.resolvers.base import BaseMutationResolver, BaseSubscriptionResolver +from api.services.base import BaseRepository +from api.services.checksum import validate_checksum +from api.services.datetime import normalize_datetime_to_utc +from api.services.property import PropertyService from api.types.task import TaskType from database import models -from database.session import publish_to_redis, redis_client from sqlalchemy import desc, select -from .utils import process_properties - @strawberry.type class TaskQuery: @strawberry.field async def task(self, info: Info, id: strawberry.ID) -> TaskType | None: - result = await info.context.db.execute( - select(models.Task).where(models.Task.id == id), - ) - return result.scalars().first() + repo = BaseRepository(info.context.db, models.Task) + return await repo.get_by_id(id) @strawberry.field async def tasks( @@ -54,36 +52,34 @@ async def recent_tasks( @strawberry.type -class TaskMutation: +class TaskMutation(BaseMutationResolver[models.Task]): + def __init__(self): + super().__init__(models.Task, "task") + + def _get_property_service(self, db) -> PropertyService: + return PropertyService(db) + @strawberry.mutation @audit_log("create_task") async def create_task(self, info: Info, data: CreateTaskInput) -> TaskType: - due_date = data.due_date - if due_date and due_date.tzinfo is not None: - due_date = due_date.astimezone(timezone.utc).replace(tzinfo=None) - new_task = models.Task( title=data.title, description=data.description, patient_id=data.patient_id, assignee_id=data.assignee_id, - due_date=due_date, + due_date=normalize_datetime_to_utc(data.due_date), ) - info.context.db.add(new_task) - if data.properties: - await process_properties( - info.context.db, - new_task, - data.properties, - "task", - ) - await info.context.db.commit() - await info.context.db.refresh(new_task) - await publish_to_redis("task_created", new_task.id) - if new_task.patient_id: - await publish_to_redis("patient_updated", new_task.patient_id) - return new_task + if data.properties: + property_service = self._get_property_service(info.context.db) + await property_service.process_properties(new_task, data.properties, "task") + + return await self.create_and_notify( + info, + new_task, + "patient" if new_task.patient_id else None, + new_task.patient_id if new_task.patient_id else None, + ) @strawberry.mutation @audit_log("update_task") @@ -93,31 +89,11 @@ async def update_task( id: strawberry.ID, data: UpdateTaskInput, ) -> TaskType: - db = info.context.db - result = await db.execute( - select(models.Task).where(models.Task.id == id), - ) - task = result.scalars().first() - if not task: - raise Exception("Task not found") + repo = self.get_repository(info.context.db) + task = await repo.get_by_id_or_raise(id, "Task not found") if data.checksum: - task_type = TaskType( - id=task.id, - title=task.title, - description=task.description, - done=task.done, - due_date=task.due_date, - creation_date=task.creation_date, - update_date=task.update_date, - assignee_id=task.assignee_id, - patient_id=task.patient_id, - ) - current_checksum = task_type.checksum - if data.checksum != current_checksum: - raise Exception( - f"CONFLICT: Task data has been modified. Expected checksum: {current_checksum}, Got: {data.checksum}" - ) + validate_checksum(task, data.checksum, "Task") if data.title is not None: task.title = data.title @@ -127,25 +103,29 @@ async def update_task( task.done = data.done if data.due_date is not strawberry.UNSET: - if data.due_date is not None: - if data.due_date.tzinfo is not None: - task.due_date = data.due_date.astimezone(timezone.utc).replace( - tzinfo=None, - ) - else: - task.due_date = data.due_date - else: - task.due_date = None + task.due_date = normalize_datetime_to_utc(data.due_date) if data.due_date else None if data.properties: - await process_properties(db, task, data.properties, "task") + property_service = self._get_property_service(info.context.db) + await property_service.process_properties(task, data.properties, "task") + + return await self.update_and_notify( + info, + task, + "patient", + task.patient_id, + ) - await db.commit() - await db.refresh(task) - await publish_to_redis("task_updated", task.id) - if task.patient_id: - await publish_to_redis("patient_updated", task.patient_id) - return task + async def _update_task_field( + self, + info: Info, + id: strawberry.ID, + field_updater, + ) -> TaskType: + repo = self.get_repository(info.context.db) + task = await repo.get_by_id_or_raise(id, "Task not found") + field_updater(task) + return await self.update_and_notify(info, task, "patient", task.patient_id) @strawberry.mutation @audit_log("assign_task") @@ -155,115 +135,61 @@ async def assign_task( id: strawberry.ID, user_id: strawberry.ID, ) -> TaskType: - db = info.context.db - result = await db.execute( - select(models.Task).where(models.Task.id == id), + return await self._update_task_field( + info, + id, + lambda task: setattr(task, "assignee_id", user_id), ) - task = result.scalars().first() - if not task: - raise Exception("Task not found") - - task.assignee_id = user_id - await db.commit() - await db.refresh(task) - await publish_to_redis("task_updated", task.id) - if task.patient_id: - await publish_to_redis("patient_updated", task.patient_id) - return task @strawberry.mutation @audit_log("unassign_task") async def unassign_task(self, info: Info, id: strawberry.ID) -> TaskType: - db = info.context.db - result = await db.execute( - select(models.Task).where(models.Task.id == id), + return await self._update_task_field( + info, + id, + lambda task: setattr(task, "assignee_id", None), ) - task = result.scalars().first() - if not task: - raise Exception("Task not found") - - task.assignee_id = None - await db.commit() - await db.refresh(task) - await publish_to_redis("task_updated", task.id) - if task.patient_id: - await publish_to_redis("patient_updated", task.patient_id) - return task @strawberry.mutation @audit_log("complete_task") async def complete_task(self, info: Info, id: strawberry.ID) -> TaskType: - db = info.context.db - result = await db.execute( - select(models.Task).where(models.Task.id == id), + return await self._update_task_field( + info, + id, + lambda task: setattr(task, "done", True), ) - task = result.scalars().first() - if not task: - raise Exception("Task not found") - - task.done = True - await db.commit() - await db.refresh(task) - await publish_to_redis("task_updated", task.id) - if task.patient_id: - await publish_to_redis("patient_updated", task.patient_id) - return task @strawberry.mutation @audit_log("reopen_task") async def reopen_task(self, info: Info, id: strawberry.ID) -> TaskType: - db = info.context.db - result = await db.execute( - select(models.Task).where(models.Task.id == id), + return await self._update_task_field( + info, + id, + lambda task: setattr(task, "done", False), ) - task = result.scalars().first() - if not task: - raise Exception("Task not found") - - task.done = False - await db.commit() - await db.refresh(task) - await publish_to_redis("task_updated", task.id) - if task.patient_id: - await publish_to_redis("patient_updated", task.patient_id) - return task @strawberry.mutation @audit_log("delete_task") async def delete_task(self, info: Info, id: strawberry.ID) -> bool: - db = info.context.db - result = await db.execute( - select(models.Task).where(models.Task.id == id), - ) - task = result.scalars().first() + repo = self.get_repository(info.context.db) + task = await repo.get_by_id(id) if not task: return False - task_id = task.id patient_id = task.patient_id - await db.delete(task) - await db.commit() - await publish_to_redis("task_deleted", task_id) - if patient_id: - await publish_to_redis("patient_updated", patient_id) + await self.delete_entity(info, task, "patient", patient_id) return True @strawberry.type -class TaskSubscription: +class TaskSubscription(BaseSubscriptionResolver): + def __init__(self): + super().__init__("task") + @strawberry.subscription - async def task_created( - self, - info: Info, - ) -> AsyncGenerator[strawberry.ID, None]: - pubsub = redis_client.pubsub() - await pubsub.subscribe("task_created") - try: - async for message in pubsub.listen(): - if message["type"] == "message": - yield message["data"] - finally: - await pubsub.close() + async def task_created(self, info: Info) -> AsyncGenerator[strawberry.ID, None]: + async for task_id in self.entity_created(info): + yield task_id @strawberry.subscription async def task_updated( @@ -271,27 +197,10 @@ async def task_updated( info: Info, task_id: strawberry.ID | None = None, ) -> AsyncGenerator[strawberry.ID, None]: - pubsub = redis_client.pubsub() - await pubsub.subscribe("task_updated") - try: - async for message in pubsub.listen(): - if message["type"] == "message": - task_id_str = message["data"] - if task_id is None or task_id_str == task_id: - yield task_id_str - finally: - await pubsub.close() + async for updated_id in self.entity_updated(info, task_id): + yield updated_id @strawberry.subscription - async def task_deleted( - self, - info: Info, - ) -> AsyncGenerator[strawberry.ID, None]: - pubsub = redis_client.pubsub() - await pubsub.subscribe("task_deleted") - try: - async for message in pubsub.listen(): - if message["type"] == "message": - yield message["data"] - finally: - await pubsub.close() + async def task_deleted(self, info: Info) -> AsyncGenerator[strawberry.ID, None]: + async for task_id in self.entity_deleted(info): + yield task_id diff --git a/backend/api/resolvers/utils.py b/backend/api/resolvers/utils.py deleted file mode 100644 index af1314ae..00000000 --- a/backend/api/resolvers/utils.py +++ /dev/null @@ -1,36 +0,0 @@ -from api.inputs import PropertyValueInput -from database import models -from sqlalchemy.ext.asyncio import AsyncSession - - -async def process_properties( - db: AsyncSession, - entity, - props_data: list[PropertyValueInput], - entity_kind: str, -): - if not props_data: - return - for p_in in props_data: - ms_val = ( - ",".join(p_in.multi_select_values) - if p_in.multi_select_values - else None - ) - prop_val = models.PropertyValue( - definition_id=p_in.definition_id, - text_value=p_in.text_value, - number_value=p_in.number_value, - boolean_value=p_in.boolean_value, - date_value=p_in.date_value, - date_time_value=p_in.date_time_value, - select_value=p_in.select_value, - multi_select_values=ms_val, - ) - - if entity_kind == "patient": - prop_val.patient_id = entity.id - elif entity_kind == "task": - prop_val.task_id = entity.id - - db.add(prop_val) diff --git a/backend/api/services/__init__.py b/backend/api/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/api/services/base.py b/backend/api/services/base.py new file mode 100644 index 00000000..05f5ff64 --- /dev/null +++ b/backend/api/services/base.py @@ -0,0 +1,54 @@ +from typing import Generic, TypeVar + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +ModelType = TypeVar("ModelType") + + +class BaseRepository(Generic[ModelType]): + def __init__(self, db: AsyncSession, model: type[ModelType]): + self.db = db + self.model = model + + async def get_by_id(self, id: str) -> ModelType | None: + result = await self.db.execute( + select(self.model).where(self.model.id == id), + ) + return result.scalars().first() + + async def get_by_id_or_raise(self, id: str, error_message: str = "Entity not found") -> ModelType: + entity = await self.get_by_id(id) + if not entity: + raise Exception(error_message) + return entity + + async def get_all(self) -> list[ModelType]: + result = await self.db.execute(select(self.model)) + return list(result.scalars().all()) + + async def create(self, entity: ModelType) -> ModelType: + self.db.add(entity) + await self.db.commit() + await self.db.refresh(entity) + return entity + + async def update(self, entity: ModelType) -> ModelType: + await self.db.commit() + await self.db.refresh(entity) + return entity + + async def delete(self, entity: ModelType) -> None: + await self.db.delete(entity) + await self.db.commit() + + +class BaseService: + def __init__(self, db: AsyncSession): + self.db = db + + async def commit_and_refresh(self, entity) -> None: + await self.db.commit() + await self.db.refresh(entity) + + diff --git a/backend/api/services/checksum.py b/backend/api/services/checksum.py new file mode 100644 index 00000000..49ff590f --- /dev/null +++ b/backend/api/services/checksum.py @@ -0,0 +1,21 @@ +from typing import Any + +from api.types.base import calculate_checksum_for_instance + + +def validate_checksum( + entity: Any, + provided_checksum: str, + entity_name: str = "Entity", +) -> None: + if not provided_checksum: + return + + current_checksum = calculate_checksum_for_instance(entity) + + if provided_checksum != current_checksum: + raise Exception( + f"CONFLICT: {entity_name} data has been modified. " + f"Expected checksum: {current_checksum}, Got: {provided_checksum}" + ) + diff --git a/backend/api/services/datetime.py b/backend/api/services/datetime.py new file mode 100644 index 00000000..fcb52e67 --- /dev/null +++ b/backend/api/services/datetime.py @@ -0,0 +1,11 @@ +from datetime import timezone + + +def normalize_datetime_to_utc(dt) -> None | object: + if dt is None: + return None + if dt.tzinfo is not None: + return dt.astimezone(timezone.utc).replace(tzinfo=None) + return dt + + diff --git a/backend/api/services/location.py b/backend/api/services/location.py new file mode 100644 index 00000000..d518a6be --- /dev/null +++ b/backend/api/services/location.py @@ -0,0 +1,81 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from database import models +from .validation import LocationValidator + + +class LocationService: + def __init__(self, db: AsyncSession): + self.db = db + self.validator = LocationValidator() + + async def get_location_by_id(self, location_id: str) -> models.LocationNode | None: + result = await self.db.execute( + select(models.LocationNode).where(models.LocationNode.id == location_id), + ) + return result.scalars().first() + + async def get_location_by_id_or_raise( + self, + location_id: str, + error_message: str | None = None, + ) -> models.LocationNode: + location = await self.get_location_by_id(location_id) + if not location: + raise Exception( + error_message or f"Location with id {location_id} not found" + ) + return location + + async def get_locations_by_ids( + self, + location_ids: list[str], + ) -> list[models.LocationNode]: + if not location_ids: + return [] + result = await self.db.execute( + select(models.LocationNode).where(models.LocationNode.id.in_(location_ids)), + ) + return list(result.scalars().all()) + + async def validate_and_get_clinic( + self, + clinic_id: str, + ) -> models.LocationNode: + clinic = await self.get_location_by_id_or_raise( + clinic_id, + f"Clinic location with id {clinic_id} not found", + ) + self.validator.validate_kind(clinic, "CLINIC", "clinic_id") + return clinic + + async def validate_and_get_position( + self, + position_id: str | None, + ) -> models.LocationNode | None: + if not position_id: + return None + position = await self.get_location_by_id_or_raise( + position_id, + f"Position location with id {position_id} not found", + ) + self.validator.validate_position_kind(position, "position_id") + return position + + async def validate_and_get_teams( + self, + team_ids: list[str], + ) -> list[models.LocationNode]: + if not team_ids: + return [] + teams = await self.get_locations_by_ids(team_ids) + if len(teams) != len(team_ids): + found_ids = {t.id for t in teams} + missing_ids = set(team_ids) - found_ids + raise Exception(f"Team locations with ids {missing_ids} not found") + for team in teams: + self.validator.validate_team_kind(team, "team_ids") + return teams + + diff --git a/backend/api/services/notifications.py b/backend/api/services/notifications.py new file mode 100644 index 00000000..fd316b24 --- /dev/null +++ b/backend/api/services/notifications.py @@ -0,0 +1,28 @@ +from database.session import publish_to_redis + + +async def notify_entity_update( + entity_type: str, + entity_id: str, + related_entity_type: str | None = None, + related_entity_id: str | None = None, +) -> None: + await publish_to_redis(f"{entity_type}_updated", str(entity_id)) + if related_entity_type and related_entity_id: + await publish_to_redis(f"{related_entity_type}_updated", str(related_entity_id)) + + +async def notify_entity_created(entity_type: str, entity_id: str) -> None: + await publish_to_redis(f"{entity_type}_created", str(entity_id)) + + +async def notify_entity_deleted( + entity_type: str, + entity_id: str, + related_entity_type: str | None = None, + related_entity_id: str | None = None, +) -> None: + await publish_to_redis(f"{entity_type}_deleted", str(entity_id)) + if related_entity_type and related_entity_id: + await publish_to_redis(f"{related_entity_type}_updated", str(related_entity_id)) + diff --git a/backend/api/services/property.py b/backend/api/services/property.py new file mode 100644 index 00000000..4b1c6ece --- /dev/null +++ b/backend/api/services/property.py @@ -0,0 +1,43 @@ +from api.inputs import PropertyValueInput +from database import models +from sqlalchemy.ext.asyncio import AsyncSession + + +class PropertyService: + def __init__(self, db: AsyncSession): + self.db = db + + async def process_properties( + self, + entity: models.Patient | models.Task, + props_data: list[PropertyValueInput], + entity_kind: str, + ) -> None: + if not props_data: + return + + for prop_input in props_data: + multi_select_value = ( + ",".join(prop_input.multi_select_values) + if prop_input.multi_select_values + else None + ) + + prop_value = models.PropertyValue( + definition_id=prop_input.definition_id, + text_value=prop_input.text_value, + number_value=prop_input.number_value, + boolean_value=prop_input.boolean_value, + date_value=prop_input.date_value, + date_time_value=prop_input.date_time_value, + select_value=prop_input.select_value, + multi_select_values=multi_select_value, + ) + + if entity_kind == "patient": + prop_value.patient_id = entity.id + elif entity_kind == "task": + prop_value.task_id = entity.id + + self.db.add(prop_value) + diff --git a/backend/api/services/subscription.py b/backend/api/services/subscription.py new file mode 100644 index 00000000..8886abed --- /dev/null +++ b/backend/api/services/subscription.py @@ -0,0 +1,21 @@ +from collections.abc import AsyncGenerator + +from database.session import redis_client + + +async def create_redis_subscription( + channel: str, + filter_id: str | None = None, +) -> AsyncGenerator[str, None]: + pubsub = redis_client.pubsub() + await pubsub.subscribe(channel) + try: + async for message in pubsub.listen(): + if message["type"] == "message": + message_id = message["data"] + if filter_id is None or message_id == filter_id: + yield message_id + finally: + await pubsub.close() + + diff --git a/backend/api/services/validation.py b/backend/api/services/validation.py new file mode 100644 index 00000000..7c666e10 --- /dev/null +++ b/backend/api/services/validation.py @@ -0,0 +1,31 @@ +from database import models + + +class LocationValidator: + @staticmethod + def validate_kind(location: models.LocationNode, expected_kind: str, field_name: str) -> None: + if location.kind.upper() != expected_kind.upper(): + raise Exception( + f"{field_name} must be a location of kind {expected_kind}, " + f"but got {location.kind}" + ) + + @staticmethod + def validate_position_kind(location: models.LocationNode, field_name: str) -> None: + allowed_kinds = {"HOSPITAL", "PRACTICE", "CLINIC", "WARD", "BED", "ROOM"} + if location.kind.upper() not in allowed_kinds: + raise Exception( + f"{field_name} must be a location of kind HOSPITAL, PRACTICE, CLINIC, " + f"WARD, BED, or ROOM, but got {location.kind}" + ) + + @staticmethod + def validate_team_kind(location: models.LocationNode, field_name: str) -> None: + allowed_kinds = {"CLINIC", "TEAM", "PRACTICE", "HOSPITAL"} + if location.kind.upper() not in allowed_kinds: + raise Exception( + f"{field_name} must be a location of kind CLINIC, TEAM, PRACTICE, " + f"or HOSPITAL, but got {location.kind}" + ) + + diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 00000000..fe2ec151 --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,8 @@ +[pytest] +asyncio_mode = auto +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* + + diff --git a/backend/requirements.txt b/backend/requirements.txt index 2ae83829..4521faa9 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -9,3 +9,7 @@ sqlalchemy==2.0.45 strawberry-graphql[fastapi]==0.287.3 uvicorn[standard]==0.38.0 influxdb_client==1.49.0 +pytest==8.3.4 +pytest-asyncio==0.24.0 +aiosqlite==0.20.0 +httpx==0.27.2 diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 00000000..cadcdf42 --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,94 @@ +import pytest +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker +from sqlalchemy.pool import StaticPool + +from database.models.base import Base +from database.models.location import LocationNode +from database.models.patient import Patient +from database.models.task import Task +from database.models.user import User +from api.inputs import Sex, PatientState + + +@pytest.fixture +async def db_session(): + engine = create_async_engine( + "sqlite+aiosqlite:///:memory:", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + async_session = async_sessionmaker(engine, expire_on_commit=False) + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + async with async_session() as session: + yield session + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + await engine.dispose() + + +@pytest.fixture +async def sample_user(db_session: AsyncSession) -> User: + user = User( + id="user-1", + username="testuser", + firstname="Test", + lastname="User", + title="Dr.", + ) + db_session.add(user) + await db_session.commit() + await db_session.refresh(user) + return user + + +@pytest.fixture +async def sample_location(db_session: AsyncSession) -> LocationNode: + location = LocationNode( + id="location-1", + title="Test Clinic", + kind="CLINIC", + ) + db_session.add(location) + await db_session.commit() + await db_session.refresh(location) + return location + + +@pytest.fixture +async def sample_patient(db_session: AsyncSession, sample_location: LocationNode) -> Patient: + from datetime import date + + patient = Patient( + id="patient-1", + firstname="John", + lastname="Doe", + birthdate=date(1990, 1, 1), + sex=Sex.MALE.value, + state=PatientState.ADMITTED.value, + clinic_id=sample_location.id, + ) + db_session.add(patient) + await db_session.commit() + await db_session.refresh(patient) + return patient + + +@pytest.fixture +async def sample_task(db_session: AsyncSession, sample_patient: Patient) -> Task: + task = Task( + id="task-1", + title="Test Task", + description="Test Description", + patient_id=sample_patient.id, + done=False, + ) + db_session.add(task) + await db_session.commit() + await db_session.refresh(task) + return task + diff --git a/backend/tests/integration/__init__.py b/backend/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/integration/test_patient_resolver.py b/backend/tests/integration/test_patient_resolver.py new file mode 100644 index 00000000..be61d32d --- /dev/null +++ b/backend/tests/integration/test_patient_resolver.py @@ -0,0 +1,77 @@ +import pytest +from api.context import Context +from api.resolvers.patient import PatientQuery, PatientMutation +from database.models.patient import Patient +from api.inputs import Sex, PatientState + + +class MockInfo: + def __init__(self, db): + self.context = Context(db=db) + + +@pytest.mark.asyncio +async def test_patient_query_get_patient(db_session, sample_patient): + info = MockInfo(db_session) + query = PatientQuery() + result = await query.patient(info, sample_patient.id) + assert result is not None + assert result.id == sample_patient.id + assert result.firstname == sample_patient.firstname + + +@pytest.mark.asyncio +async def test_patient_query_patients(db_session, sample_patient): + info = MockInfo(db_session) + query = PatientQuery() + results = await query.patients(info) + assert len(results) >= 1 + assert any(p.id == sample_patient.id for p in results) + + +@pytest.mark.asyncio +async def test_patient_mutation_create_patient(db_session, sample_location): + from api.inputs import CreatePatientInput + + info = MockInfo(db_session) + mutation = PatientMutation() + input_data = CreatePatientInput( + firstname="Jane", + lastname="Doe", + sex=Sex.FEMALE, + clinic_id=sample_location.id, + ) + result = await mutation.create_patient(info, input_data) + assert result.id is not None + assert result.firstname == "Jane" + assert result.lastname == "Doe" + assert result.clinic_id == sample_location.id + + +@pytest.mark.asyncio +async def test_patient_mutation_update_patient(db_session, sample_patient): + from api.inputs import UpdatePatientInput + + info = MockInfo(db_session) + mutation = PatientMutation() + input_data = UpdatePatientInput(firstname="Updated Name") + result = await mutation.update_patient(info, sample_patient.id, input_data) + assert result.firstname == "Updated Name" + assert result.id == sample_patient.id + + +@pytest.mark.asyncio +async def test_patient_mutation_admit_patient(db_session, sample_patient): + info = MockInfo(db_session) + mutation = PatientMutation() + result = await mutation.admit_patient(info, sample_patient.id) + assert result.state == PatientState.ADMITTED.value + + +@pytest.mark.asyncio +async def test_patient_mutation_discharge_patient(db_session, sample_patient): + info = MockInfo(db_session) + mutation = PatientMutation() + result = await mutation.discharge_patient(info, sample_patient.id) + assert result.state == PatientState.DISCHARGED.value + diff --git a/backend/tests/integration/test_task_resolver.py b/backend/tests/integration/test_task_resolver.py new file mode 100644 index 00000000..a3c18e1c --- /dev/null +++ b/backend/tests/integration/test_task_resolver.py @@ -0,0 +1,91 @@ +import pytest +from api.context import Context, Info +from api.resolvers.task import TaskQuery, TaskMutation +from database.models.task import Task +from typing import Any +import strawberry + + +class MockInfo: + def __init__(self, db): + self.context = Context(db=db) + + +@pytest.mark.asyncio +async def test_task_query_get_task(db_session, sample_task): + info = MockInfo(db_session) + query = TaskQuery() + result = await query.task(info, sample_task.id) + assert result is not None + assert result.id == sample_task.id + assert result.title == sample_task.title + + +@pytest.mark.asyncio +async def test_task_query_tasks_by_patient(db_session, sample_patient): + info = MockInfo(db_session) + task1 = Task(title="Task 1", patient_id=sample_patient.id) + task2 = Task(title="Task 2", patient_id=sample_patient.id) + db_session.add(task1) + db_session.add(task2) + await db_session.commit() + + query = TaskQuery() + results = await query.tasks(info, patient_id=sample_patient.id) + assert len(results) >= 2 + task_titles = {t.title for t in results} + assert "Task 1" in task_titles + assert "Task 2" in task_titles + + +@pytest.mark.asyncio +async def test_task_mutation_create_task(db_session, sample_patient): + from api.inputs import CreateTaskInput + + info = MockInfo(db_session) + mutation = TaskMutation() + input_data = CreateTaskInput( + title="New Task", + description="Description", + patient_id=sample_patient.id, + ) + result = await mutation.create_task(info, input_data) + assert result.id is not None + assert result.title == "New Task" + assert result.patient_id == sample_patient.id + + +@pytest.mark.asyncio +async def test_task_mutation_update_task(db_session, sample_task): + from api.inputs import UpdateTaskInput + + info = MockInfo(db_session) + mutation = TaskMutation() + input_data = UpdateTaskInput(title="Updated Title") + result = await mutation.update_task(info, sample_task.id, input_data) + assert result.title == "Updated Title" + assert result.id == sample_task.id + + +@pytest.mark.asyncio +async def test_task_mutation_complete_task(db_session, sample_task): + info = MockInfo(db_session) + mutation = TaskMutation() + result = await mutation.complete_task(info, sample_task.id) + assert result.done is True + assert result.id == sample_task.id + + +@pytest.mark.asyncio +async def test_task_mutation_delete_task(db_session, sample_task): + info = MockInfo(db_session) + mutation = TaskMutation() + task_id = sample_task.id + result = await mutation.delete_task(info, task_id) + assert result is True + + query = TaskQuery() + task = await query.task(info, task_id) + assert task is None + + diff --git a/backend/tests/unit/__init__.py b/backend/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/unit/test_base_repository.py b/backend/tests/unit/test_base_repository.py new file mode 100644 index 00000000..fd94c0f4 --- /dev/null +++ b/backend/tests/unit/test_base_repository.py @@ -0,0 +1,73 @@ +import pytest +from api.services.base import BaseRepository +from database.models.task import Task + + +@pytest.mark.asyncio +async def test_get_by_id(db_session, sample_task): + repo = BaseRepository(db_session, Task) + result = await repo.get_by_id(sample_task.id) + assert result is not None + assert result.id == sample_task.id + assert result.title == "Test Task" + + +@pytest.mark.asyncio +async def test_get_by_id_not_found(db_session): + repo = BaseRepository(db_session, Task) + result = await repo.get_by_id("non-existent") + assert result is None + + +@pytest.mark.asyncio +async def test_get_by_id_or_raise(db_session, sample_task): + repo = BaseRepository(db_session, Task) + result = await repo.get_by_id_or_raise(sample_task.id) + assert result.id == sample_task.id + + +@pytest.mark.asyncio +async def test_get_by_id_or_raise_not_found(db_session): + repo = BaseRepository(db_session, Task) + with pytest.raises(Exception, match="Entity not found"): + await repo.get_by_id_or_raise("non-existent") + + +@pytest.mark.asyncio +async def test_get_all(db_session, sample_task): + repo = BaseRepository(db_session, Task) + results = await repo.get_all() + assert len(results) >= 1 + assert any(t.id == sample_task.id for t in results) + + +@pytest.mark.asyncio +async def test_create(db_session): + repo = BaseRepository(db_session, Task) + new_task = Task( + title="New Task", + description="New Description", + patient_id="patient-1", + ) + result = await repo.create(new_task) + assert result.id is not None + assert result.title == "New Task" + + +@pytest.mark.asyncio +async def test_update(db_session, sample_task): + repo = BaseRepository(db_session, Task) + sample_task.title = "Updated Title" + result = await repo.update(sample_task) + assert result.title == "Updated Title" + + +@pytest.mark.asyncio +async def test_delete(db_session, sample_task): + repo = BaseRepository(db_session, Task) + task_id = sample_task.id + await repo.delete(sample_task) + result = await repo.get_by_id(task_id) + assert result is None + + diff --git a/backend/tests/unit/test_base_resolvers.py b/backend/tests/unit/test_base_resolvers.py new file mode 100644 index 00000000..44313466 --- /dev/null +++ b/backend/tests/unit/test_base_resolvers.py @@ -0,0 +1,58 @@ +import pytest +from api.context import Context +from api.resolvers.base import BaseMutationResolver, BaseQueryResolver +from database.models.task import Task + + +class MockInfo: + def __init__(self, db): + self.context = Context(db=db) + + +@pytest.mark.asyncio +async def test_base_query_resolver_get_by_id(db_session, sample_task): + resolver = BaseQueryResolver(Task) + info = MockInfo(db_session) + result = await resolver.get_by_id(info, sample_task.id) + assert result is not None + assert result.id == sample_task.id + + +@pytest.mark.asyncio +async def test_base_query_resolver_get_all(db_session, sample_task): + resolver = BaseQueryResolver(Task) + info = MockInfo(db_session) + results = await resolver.get_all(info) + assert len(results) >= 1 + assert any(t.id == sample_task.id for t in results) + + +@pytest.mark.asyncio +async def test_base_mutation_resolver_create_and_notify(db_session): + resolver = BaseMutationResolver(Task, "task") + info = MockInfo(db_session) + new_task = Task(title="New Task", description="Description") + result = await resolver.create_and_notify(info, new_task) + assert result.id is not None + assert result.title == "New Task" + + +@pytest.mark.asyncio +async def test_base_mutation_resolver_update_and_notify(db_session, sample_task): + resolver = BaseMutationResolver(Task, "task") + info = MockInfo(db_session) + sample_task.title = "Updated Title" + result = await resolver.update_and_notify(info, sample_task) + assert result.title == "Updated Title" + + +@pytest.mark.asyncio +async def test_base_mutation_resolver_delete_entity(db_session, sample_task): + resolver = BaseMutationResolver(Task, "task") + info = MockInfo(db_session) + await resolver.delete_entity(info, sample_task) + + repo = resolver.get_repository(db_session) + result = await repo.get_by_id(sample_task.id) + assert result is None + diff --git a/backend/tests/unit/test_checksum.py b/backend/tests/unit/test_checksum.py new file mode 100644 index 00000000..88c10cc4 --- /dev/null +++ b/backend/tests/unit/test_checksum.py @@ -0,0 +1,24 @@ +import pytest +from api.services.checksum import validate_checksum +from database.models.task import Task + + +@pytest.mark.asyncio +async def test_validate_checksum_valid(db_session, sample_task): + from api.types.base import calculate_checksum_for_instance + + checksum = calculate_checksum_for_instance(sample_task) + validate_checksum(sample_task, checksum, "Task") + + +@pytest.mark.asyncio +async def test_validate_checksum_invalid(db_session, sample_task): + with pytest.raises(Exception, match="CONFLICT"): + validate_checksum(sample_task, "invalid-checksum", "Task") + + +@pytest.mark.asyncio +async def test_validate_checksum_none(db_session, sample_task): + validate_checksum(sample_task, None, "Task") + + diff --git a/backend/tests/unit/test_location_service.py b/backend/tests/unit/test_location_service.py new file mode 100644 index 00000000..81d93d5c --- /dev/null +++ b/backend/tests/unit/test_location_service.py @@ -0,0 +1,100 @@ +import pytest +from api.services.location import LocationService +from database.models.location import LocationNode + + +@pytest.mark.asyncio +async def test_get_location_by_id(db_session, sample_location): + service = LocationService(db_session) + result = await service.get_location_by_id(sample_location.id) + assert result is not None + assert result.id == sample_location.id + + +@pytest.mark.asyncio +async def test_get_location_by_id_not_found(db_session): + service = LocationService(db_session) + result = await service.get_location_by_id("non-existent") + assert result is None + + +@pytest.mark.asyncio +async def test_get_location_by_id_or_raise(db_session, sample_location): + service = LocationService(db_session) + result = await service.get_location_by_id_or_raise(sample_location.id) + assert result.id == sample_location.id + + +@pytest.mark.asyncio +async def test_get_location_by_id_or_raise_not_found(db_session): + service = LocationService(db_session) + with pytest.raises(Exception, match="not found"): + await service.get_location_by_id_or_raise("non-existent") + + +@pytest.mark.asyncio +async def test_get_locations_by_ids(db_session, sample_location): + service = LocationService(db_session) + location2 = LocationNode(id="location-2", title="Location 2", kind="WARD") + db_session.add(location2) + await db_session.commit() + + results = await service.get_locations_by_ids([sample_location.id, "location-2"]) + assert len(results) == 2 + + +@pytest.mark.asyncio +async def test_validate_and_get_clinic(db_session, sample_location): + service = LocationService(db_session) + result = await service.validate_and_get_clinic(sample_location.id) + assert result.id == sample_location.id + assert result.kind == "CLINIC" + + +@pytest.mark.asyncio +async def test_validate_and_get_clinic_wrong_kind(db_session): + service = LocationService(db_session) + ward = LocationNode(id="ward-1", title="Ward", kind="WARD") + db_session.add(ward) + await db_session.commit() + + with pytest.raises(Exception, match="must be a location of kind CLINIC"): + await service.validate_and_get_clinic("ward-1") + + +@pytest.mark.asyncio +async def test_validate_and_get_position(db_session): + service = LocationService(db_session) + ward = LocationNode(id="ward-1", title="Ward", kind="WARD") + db_session.add(ward) + await db_session.commit() + + result = await service.validate_and_get_position("ward-1") + assert result is not None + assert result.kind == "WARD" + + +@pytest.mark.asyncio +async def test_validate_and_get_teams(db_session): + service = LocationService(db_session) + team1 = LocationNode(id="team-1", title="Team 1", kind="TEAM") + team2 = LocationNode(id="team-2", title="Team 2", kind="TEAM") + db_session.add(team1) + db_session.add(team2) + await db_session.commit() + + results = await service.validate_and_get_teams(["team-1", "team-2"]) + assert len(results) == 2 + + +@pytest.mark.asyncio +async def test_validate_and_get_teams_missing(db_session): + service = LocationService(db_session) + team1 = LocationNode(id="team-1", title="Team 1", kind="TEAM") + db_session.add(team1) + await db_session.commit() + + with pytest.raises(Exception, match="not found"): + await service.validate_and_get_teams(["team-1", "non-existent"]) + + diff --git a/backend/tests/unit/test_property_service.py b/backend/tests/unit/test_property_service.py new file mode 100644 index 00000000..cd92ab48 --- /dev/null +++ b/backend/tests/unit/test_property_service.py @@ -0,0 +1,88 @@ +import pytest +from api.inputs import PropertyValueInput +from api.services.property import PropertyService +from database.models.patient import Patient +from database.models.task import Task +from api.inputs import Sex, PatientState + + +@pytest.mark.asyncio +async def test_process_properties_for_patient(db_session, sample_patient): + service = PropertyService(db_session) + props = [ + PropertyValueInput( + definition_id="def-1", + text_value="Test Value", + ), + ] + await service.process_properties(sample_patient, props, "patient") + await db_session.commit() + + from database.models.property import PropertyValue + from sqlalchemy import select + result = await db_session.execute( + select(PropertyValue).where(PropertyValue.patient_id == sample_patient.id) + ) + prop_values = result.scalars().all() + assert len(prop_values) == 1 + assert prop_values[0].text_value == "Test Value" + + +@pytest.mark.asyncio +async def test_process_properties_for_task(db_session, sample_task): + service = PropertyService(db_session) + props = [ + PropertyValueInput( + definition_id="def-1", + number_value=42, + ), + ] + await service.process_properties(sample_task, props, "task") + await db_session.commit() + + from database.models.property import PropertyValue + from sqlalchemy import select + result = await db_session.execute( + select(PropertyValue).where(PropertyValue.task_id == sample_task.id) + ) + prop_values = result.scalars().all() + assert len(prop_values) == 1 + assert prop_values[0].number_value == 42 + + +@pytest.mark.asyncio +async def test_process_properties_empty_list(db_session, sample_patient): + service = PropertyService(db_session) + await service.process_properties(sample_patient, [], "patient") + await db_session.commit() + + from database.models.property import PropertyValue + from sqlalchemy import select + result = await db_session.execute( + select(PropertyValue).where(PropertyValue.patient_id == sample_patient.id) + ) + prop_values = result.scalars().all() + assert len(prop_values) == 0 + + +@pytest.mark.asyncio +async def test_process_properties_multi_select(db_session, sample_patient): + service = PropertyService(db_session) + props = [ + PropertyValueInput( + definition_id="def-1", + multi_select_values=["option1", "option2", "option3"], + ), + ] + await service.process_properties(sample_patient, props, "patient") + await db_session.commit() + + from database.models.property import PropertyValue + from sqlalchemy import select + result = await db_session.execute( + select(PropertyValue).where(PropertyValue.patient_id == sample_patient.id) + ) + prop_values = result.scalars().all() + assert len(prop_values) == 1 + assert prop_values[0].multi_select_values == "option1,option2,option3" + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/auth.spec.ts b/tests/e2e/auth.spec.ts new file mode 100644 index 00000000..69c5e256 --- /dev/null +++ b/tests/e2e/auth.spec.ts @@ -0,0 +1,56 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Authentication', () => { + test('should display login page', async ({ page }) => { + await page.goto('/'); + await expect(page).toHaveTitle(/tasks/i); + }); + + test('should handle authentication flow', async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + const body = page.locator('body'); + await expect(body).toBeVisible(); + }); + + test('should handle unauthenticated access gracefully', async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + const errors: string[] = []; + page.on('pageerror', (error) => { + errors.push(error.message); + }); + + await page.waitForTimeout(1000); + expect(errors.length).toBeLessThanOrEqual(0); + }); + + test('should have proper page metadata', async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + const title = await page.title(); + expect(title).toBeTruthy(); + }); + + test('should load without console errors', async ({ page }) => { + const consoleErrors: string[] = []; + page.on('console', (msg) => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + const criticalErrors = consoleErrors.filter( + (error) => !error.includes('favicon') && !error.includes('404') + ); + expect(criticalErrors.length).toBeLessThanOrEqual(0); + }); +}); + + diff --git a/tests/e2e/navigation.spec.ts b/tests/e2e/navigation.spec.ts new file mode 100644 index 00000000..1653eb38 --- /dev/null +++ b/tests/e2e/navigation.spec.ts @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Navigation', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + }); + + test('should navigate between pages', async ({ page }) => { + const pages = ['/tasks', '/patients', '/properties']; + + for (const path of pages) { + await page.goto(path); + await page.waitForLoadState('networkidle'); + await expect(page.locator('body')).toBeVisible(); + } + }); + + test('should handle 404 page', async ({ page }) => { + await page.goto('/non-existent-page'); + await page.waitForLoadState('networkidle'); + + const body = page.locator('body'); + await expect(body).toBeVisible(); + }); + + test('should maintain state during navigation', async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + await page.goto('/tasks'); + await page.waitForLoadState('networkidle'); + + await page.goBack(); + await page.waitForLoadState('networkidle'); + + await expect(page).toHaveURL(/.*\/$/); + }); +}); + diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts new file mode 100644 index 00000000..3ea424c7 --- /dev/null +++ b/tests/e2e/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: process.env.E2E_BASE_URL || 'http://localhost:3000', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: { + command: 'cd web && npm run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}); + + diff --git a/tests/e2e/tasks.spec.ts b/tests/e2e/tasks.spec.ts new file mode 100644 index 00000000..23781811 --- /dev/null +++ b/tests/e2e/tasks.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Tasks', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + }); + + test('should display tasks page', async ({ page }) => { + await page.goto('/tasks'); + await page.waitForLoadState('networkidle'); + await expect(page.locator('body')).toBeVisible(); + }); + + test('should navigate to tasks page from home', async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + const tasksLink = page.locator('a[href*="/tasks"]').first(); + if (await tasksLink.isVisible()) { + await tasksLink.click(); + await page.waitForLoadState('networkidle'); + await expect(page).toHaveURL(/.*tasks/); + } + }); + + test('should handle page load without errors', async ({ page }) => { + const errors: string[] = []; + page.on('pageerror', (error) => { + errors.push(error.message); + }); + + await page.goto('/tasks'); + await page.waitForLoadState('networkidle'); + + expect(errors).toHaveLength(0); + }); + + test('should have accessible page structure', async ({ page }) => { + await page.goto('/tasks'); + await page.waitForLoadState('networkidle'); + + const mainContent = page.locator('main, [role="main"], body'); + await expect(mainContent.first()).toBeVisible(); + }); +}); + + diff --git a/web/package.json b/web/package.json index b16c0cd4..b401257a 100644 --- a/web/package.json +++ b/web/package.json @@ -42,6 +42,7 @@ "@graphql-codegen/typescript-operations": "5.0.6", "@graphql-codegen/typescript-react-query": "6.1.1", "@helpwave/eslint-config": "0.0.11", + "@playwright/test": "^1.48.0", "@types/node": "20.17.10", "@types/react": "18.3.17", "@types/react-dom": "18.3.5", From c0b1464ce0c3b65635bef29b9e06f72681836086 Mon Sep 17 00:00:00 2001 From: Felix Evers Date: Sun, 21 Dec 2025 00:58:22 +0100 Subject: [PATCH 02/45] fix builds --- backend/api/resolvers/patient.py | 7 ++----- backend/api/resolvers/property.py | 1 - backend/auth.py | 2 +- backend/tests/integration/test_patient_resolver.py | 1 - backend/tests/integration/test_task_resolver.py | 4 +--- backend/tests/unit/test_checksum.py | 1 - backend/tests/unit/test_property_service.py | 3 --- 7 files changed, 4 insertions(+), 15 deletions(-) diff --git a/backend/api/resolvers/patient.py b/backend/api/resolvers/patient.py index 31d007f3..8401c4ba 100644 --- a/backend/api/resolvers/patient.py +++ b/backend/api/resolvers/patient.py @@ -3,9 +3,8 @@ import strawberry from api.audit import audit_log from api.context import Info -from api.inputs import CreatePatientInput, PatientState, Sex, UpdatePatientInput +from api.inputs import CreatePatientInput, PatientState, UpdatePatientInput from api.resolvers.base import BaseMutationResolver, BaseSubscriptionResolver -from api.services.base import BaseRepository from api.services.checksum import validate_checksum from api.services.location import LocationService from api.services.property import PropertyService @@ -133,9 +132,8 @@ async def create_patient( await location_service.validate_and_get_clinic(data.clinic_id) - position = None if data.position_id: - position = await location_service.validate_and_get_position(data.position_id) + await location_service.validate_and_get_position(data.position_id) teams = [] if data.team_ids: @@ -181,7 +179,6 @@ async def update_patient( data: UpdatePatientInput, ) -> PatientType: db = info.context.db - repo = BaseRepository(db, models.Patient) result = await db.execute( select(models.Patient) .where(models.Patient.id == id) diff --git a/backend/api/resolvers/property.py b/backend/api/resolvers/property.py index 5da8a310..0a13befd 100644 --- a/backend/api/resolvers/property.py +++ b/backend/api/resolvers/property.py @@ -5,7 +5,6 @@ UpdatePropertyDefinitionInput, ) from api.resolvers.base import BaseMutationResolver -from api.services.base import BaseRepository from api.types.property import PropertyDefinitionType from database import models from sqlalchemy import select diff --git a/backend/auth.py b/backend/auth.py index 0e6fa1e6..14213297 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -45,7 +45,7 @@ def get_user_payload(connection: HTTPConnection) -> Optional[dict]: def get_public_key(token: str) -> Any: - global jwks_cache # noqa: F824 + global jwks_cache # noqa: PLW0603 try: header = jwt.get_unverified_header(token) diff --git a/backend/tests/integration/test_patient_resolver.py b/backend/tests/integration/test_patient_resolver.py index be61d32d..e0de85ba 100644 --- a/backend/tests/integration/test_patient_resolver.py +++ b/backend/tests/integration/test_patient_resolver.py @@ -1,7 +1,6 @@ import pytest from api.context import Context from api.resolvers.patient import PatientQuery, PatientMutation -from database.models.patient import Patient from api.inputs import Sex, PatientState diff --git a/backend/tests/integration/test_task_resolver.py b/backend/tests/integration/test_task_resolver.py index a3c18e1c..31bf4ccc 100644 --- a/backend/tests/integration/test_task_resolver.py +++ b/backend/tests/integration/test_task_resolver.py @@ -1,9 +1,7 @@ import pytest -from api.context import Context, Info +from api.context import Context from api.resolvers.task import TaskQuery, TaskMutation from database.models.task import Task -from typing import Any -import strawberry class MockInfo: diff --git a/backend/tests/unit/test_checksum.py b/backend/tests/unit/test_checksum.py index 88c10cc4..983aef37 100644 --- a/backend/tests/unit/test_checksum.py +++ b/backend/tests/unit/test_checksum.py @@ -1,6 +1,5 @@ import pytest from api.services.checksum import validate_checksum -from database.models.task import Task @pytest.mark.asyncio diff --git a/backend/tests/unit/test_property_service.py b/backend/tests/unit/test_property_service.py index cd92ab48..20bd7983 100644 --- a/backend/tests/unit/test_property_service.py +++ b/backend/tests/unit/test_property_service.py @@ -1,9 +1,6 @@ import pytest from api.inputs import PropertyValueInput from api.services.property import PropertyService -from database.models.patient import Patient -from database.models.task import Task -from api.inputs import Sex, PatientState @pytest.mark.asyncio From b757991c09143a494a7c63b4984075a28ebcff33 Mon Sep 17 00:00:00 2001 From: Felix Evers Date: Sun, 21 Dec 2025 01:01:40 +0100 Subject: [PATCH 03/45] Refactor codebase: improve structure, reduce duplication, add tests - Create base resolver classes to reduce code duplication - Refactor property processing into PropertyService - Generalize subscription patterns with base classes - Add comprehensive unit and integration tests - Enhance E2E tests with more scenarios - Create GitHub Actions workflow for CI/CD - Fix npm lock file issues for E2E tests - Add documentation for testing and local GitHub Actions execution - Remove all comments from code - Fix all linting errors and warnings --- .github/workflows/README.md | 69 + .github/workflows/tests.yml | 5 + README.md | 41 + TESTING.md | 153 + backend/api/context.py | 1 - tests/node_modules/.bin/playwright | 1 + tests/node_modules/.bin/playwright-core | 1 + tests/node_modules/.package-lock.json | 56 + tests/node_modules/@playwright/test/LICENSE | 202 + tests/node_modules/@playwright/test/NOTICE | 5 + tests/node_modules/@playwright/test/README.md | 168 + tests/node_modules/@playwright/test/cli.js | 19 + .../node_modules/@playwright/test/index.d.ts | 18 + tests/node_modules/@playwright/test/index.js | 17 + tests/node_modules/@playwright/test/index.mjs | 18 + .../@playwright/test/package.json | 35 + .../@playwright/test/reporter.d.ts | 17 + .../node_modules/@playwright/test/reporter.js | 17 + .../@playwright/test/reporter.mjs | 17 + tests/node_modules/playwright-core/LICENSE | 202 + tests/node_modules/playwright-core/NOTICE | 5 + tests/node_modules/playwright-core/README.md | 3 + .../playwright-core/ThirdPartyNotices.txt | 1161 + .../bin/install_media_pack.ps1 | 5 + .../bin/install_webkit_wsl.ps1 | 33 + .../bin/reinstall_chrome_beta_linux.sh | 42 + .../bin/reinstall_chrome_beta_mac.sh | 13 + .../bin/reinstall_chrome_beta_win.ps1 | 24 + .../bin/reinstall_chrome_stable_linux.sh | 42 + .../bin/reinstall_chrome_stable_mac.sh | 12 + .../bin/reinstall_chrome_stable_win.ps1 | 24 + .../bin/reinstall_msedge_beta_linux.sh | 48 + .../bin/reinstall_msedge_beta_mac.sh | 11 + .../bin/reinstall_msedge_beta_win.ps1 | 23 + .../bin/reinstall_msedge_dev_linux.sh | 48 + .../bin/reinstall_msedge_dev_mac.sh | 11 + .../bin/reinstall_msedge_dev_win.ps1 | 23 + .../bin/reinstall_msedge_stable_linux.sh | 48 + .../bin/reinstall_msedge_stable_mac.sh | 11 + .../bin/reinstall_msedge_stable_win.ps1 | 24 + .../playwright-core/browsers.json | 80 + tests/node_modules/playwright-core/cli.js | 18 + tests/node_modules/playwright-core/index.d.ts | 17 + tests/node_modules/playwright-core/index.js | 32 + tests/node_modules/playwright-core/index.mjs | 28 + .../playwright-core/lib/androidServerImpl.js | 65 + .../playwright-core/lib/browserServerImpl.js | 120 + .../playwright-core/lib/cli/driver.js | 97 + .../playwright-core/lib/cli/program.js | 590 + .../lib/cli/programWithTestStub.js | 74 + .../playwright-core/lib/client/android.js | 361 + .../playwright-core/lib/client/api.js | 134 + .../playwright-core/lib/client/artifact.js | 79 + .../playwright-core/lib/client/browser.js | 163 + .../lib/client/browserContext.js | 547 + .../playwright-core/lib/client/browserType.js | 184 + .../playwright-core/lib/client/cdpSession.js | 51 + .../lib/client/channelOwner.js | 194 + .../lib/client/clientHelper.js | 64 + .../lib/client/clientInstrumentation.js | 55 + .../lib/client/clientStackTrace.js | 69 + .../playwright-core/lib/client/clock.js | 68 + .../playwright-core/lib/client/connection.js | 314 + .../lib/client/consoleMessage.js | 58 + .../playwright-core/lib/client/coverage.js | 44 + .../playwright-core/lib/client/dialog.js | 56 + .../playwright-core/lib/client/download.js | 62 + .../playwright-core/lib/client/electron.js | 138 + .../lib/client/elementHandle.js | 281 + .../playwright-core/lib/client/errors.js | 77 + .../lib/client/eventEmitter.js | 314 + .../playwright-core/lib/client/events.js | 100 + .../playwright-core/lib/client/fetch.js | 369 + .../playwright-core/lib/client/fileChooser.js | 46 + .../playwright-core/lib/client/fileUtils.js | 34 + .../playwright-core/lib/client/frame.js | 408 + .../playwright-core/lib/client/harRouter.js | 87 + .../playwright-core/lib/client/input.js | 84 + .../playwright-core/lib/client/jsHandle.js | 109 + .../playwright-core/lib/client/jsonPipe.js | 39 + .../playwright-core/lib/client/localUtils.js | 60 + .../playwright-core/lib/client/locator.js | 369 + .../playwright-core/lib/client/network.js | 747 + .../playwright-core/lib/client/page.js | 718 + .../playwright-core/lib/client/platform.js | 74 + .../playwright-core/lib/client/playwright.js | 71 + .../playwright-core/lib/client/selectors.js | 55 + .../playwright-core/lib/client/stream.js | 39 + .../lib/client/timeoutSettings.js | 79 + .../playwright-core/lib/client/tracing.js | 119 + .../playwright-core/lib/client/types.js | 28 + .../playwright-core/lib/client/video.js | 59 + .../playwright-core/lib/client/waiter.js | 142 + .../playwright-core/lib/client/webError.js | 39 + .../playwright-core/lib/client/webSocket.js | 93 + .../playwright-core/lib/client/worker.js | 85 + .../lib/client/writableStream.js | 39 + .../lib/generated/bindingsControllerSource.js | 28 + .../lib/generated/clockSource.js | 28 + .../lib/generated/injectedScriptSource.js | 28 + .../lib/generated/pollingRecorderSource.js | 28 + .../lib/generated/storageScriptSource.js | 28 + .../lib/generated/utilityScriptSource.js | 28 + .../lib/generated/webSocketMockSource.js | 336 + .../playwright-core/lib/inProcessFactory.js | 60 + .../playwright-core/lib/inprocess.js | 3 + .../playwright-core/lib/outofprocess.js | 76 + .../lib/protocol/serializers.js | 192 + .../playwright-core/lib/protocol/validator.js | 2890 ++ .../lib/protocol/validatorPrimitives.js | 193 + .../lib/remote/playwrightConnection.js | 129 + .../lib/remote/playwrightServer.js | 335 + .../lib/server/android/android.js | 465 + .../lib/server/android/backendAdb.js | 177 + .../playwright-core/lib/server/artifact.js | 127 + .../lib/server/bidi/bidiBrowser.js | 505 + .../lib/server/bidi/bidiChromium.js | 153 + .../lib/server/bidi/bidiConnection.js | 212 + .../lib/server/bidi/bidiExecutionContext.js | 221 + .../lib/server/bidi/bidiFirefox.js | 130 + .../lib/server/bidi/bidiInput.js | 146 + .../lib/server/bidi/bidiNetworkManager.js | 383 + .../lib/server/bidi/bidiOverCdp.js | 102 + .../lib/server/bidi/bidiPage.js | 572 + .../lib/server/bidi/bidiPdf.js | 106 + .../server/bidi/third_party/bidiCommands.d.js | 22 + .../bidi/third_party/bidiDeserializer.js | 98 + .../server/bidi/third_party/bidiKeyboard.js | 256 + .../server/bidi/third_party/bidiProtocol.js | 24 + .../bidi/third_party/bidiProtocolCore.js | 179 + .../third_party/bidiProtocolPermissions.js | 42 + .../server/bidi/third_party/bidiSerializer.js | 148 + .../server/bidi/third_party/firefoxPrefs.js | 259 + .../playwright-core/lib/server/browser.js | 149 + .../lib/server/browserContext.js | 695 + .../playwright-core/lib/server/browserType.js | 328 + .../playwright-core/lib/server/callLog.js | 82 + .../lib/server/chromium/appIcon.png | Bin 0 -> 16565 bytes .../lib/server/chromium/chromium.js | 402 + .../lib/server/chromium/chromiumSwitches.js | 104 + .../lib/server/chromium/crBrowser.js | 510 + .../lib/server/chromium/crConnection.js | 202 + .../lib/server/chromium/crCoverage.js | 235 + .../lib/server/chromium/crDevTools.js | 113 + .../lib/server/chromium/crDragDrop.js | 131 + .../lib/server/chromium/crExecutionContext.js | 146 + .../lib/server/chromium/crInput.js | 187 + .../lib/server/chromium/crNetworkManager.js | 666 + .../lib/server/chromium/crPage.js | 1069 + .../lib/server/chromium/crPdf.js | 121 + .../lib/server/chromium/crProtocolHelper.js | 145 + .../lib/server/chromium/crServiceWorker.js | 136 + .../server/chromium/defaultFontFamilies.js | 162 + .../lib/server/chromium/protocol.d.js | 16 + .../lib/server/chromium/videoRecorder.js | 115 + .../playwright-core/lib/server/clock.js | 149 + .../lib/server/codegen/csharp.js | 327 + .../lib/server/codegen/java.js | 274 + .../lib/server/codegen/javascript.js | 270 + .../lib/server/codegen/jsonl.js | 52 + .../lib/server/codegen/language.js | 132 + .../lib/server/codegen/languages.js | 68 + .../lib/server/codegen/python.js | 279 + .../lib/server/codegen/types.js | 16 + .../playwright-core/lib/server/console.js | 57 + .../playwright-core/lib/server/cookieStore.js | 206 + .../lib/server/debugController.js | 191 + .../playwright-core/lib/server/debugger.js | 119 + .../lib/server/deviceDescriptors.js | 39 + .../lib/server/deviceDescriptorsSource.json | 1779 ++ .../playwright-core/lib/server/dialog.js | 116 + .../server/dispatchers/androidDispatcher.js | 325 + .../server/dispatchers/artifactDispatcher.js | 118 + .../dispatchers/browserContextDispatcher.js | 381 + .../server/dispatchers/browserDispatcher.js | 118 + .../dispatchers/browserTypeDispatcher.js | 64 + .../dispatchers/cdpSessionDispatcher.js | 44 + .../dispatchers/debugControllerDispatcher.js | 78 + .../server/dispatchers/dialogDispatcher.js | 47 + .../lib/server/dispatchers/dispatcher.js | 371 + .../server/dispatchers/electronDispatcher.js | 89 + .../dispatchers/elementHandlerDispatcher.js | 181 + .../lib/server/dispatchers/frameDispatcher.js | 227 + .../server/dispatchers/jsHandleDispatcher.js | 85 + .../server/dispatchers/jsonPipeDispatcher.js | 58 + .../dispatchers/localUtilsDispatcher.js | 149 + .../server/dispatchers/networkDispatchers.js | 213 + .../lib/server/dispatchers/pageDispatcher.js | 389 + .../dispatchers/playwrightDispatcher.js | 108 + .../server/dispatchers/streamDispatcher.js | 67 + .../server/dispatchers/tracingDispatcher.js | 68 + .../dispatchers/webSocketRouteDispatcher.js | 165 + .../dispatchers/writableStreamDispatcher.js | 79 + .../playwright-core/lib/server/dom.js | 806 + .../playwright-core/lib/server/download.js | 70 + .../lib/server/electron/electron.js | 270 + .../lib/server/electron/loader.js | 29 + .../playwright-core/lib/server/errors.js | 69 + .../playwright-core/lib/server/fetch.js | 621 + .../playwright-core/lib/server/fileChooser.js | 43 + .../lib/server/fileUploadUtils.js | 84 + .../lib/server/firefox/ffBrowser.js | 428 + .../lib/server/firefox/ffConnection.js | 147 + .../lib/server/firefox/ffExecutionContext.js | 150 + .../lib/server/firefox/ffInput.js | 159 + .../lib/server/firefox/ffNetworkManager.js | 256 + .../lib/server/firefox/ffPage.js | 500 + .../lib/server/firefox/firefox.js | 116 + .../lib/server/firefox/protocol.d.js | 16 + .../playwright-core/lib/server/formData.js | 147 + .../lib/server/frameSelectors.js | 154 + .../playwright-core/lib/server/frames.js | 1455 + .../lib/server/har/harRecorder.js | 147 + .../lib/server/har/harTracer.js | 607 + .../playwright-core/lib/server/harBackend.js | 157 + .../playwright-core/lib/server/helper.js | 96 + .../playwright-core/lib/server/index.js | 58 + .../playwright-core/lib/server/input.js | 277 + .../lib/server/instrumentation.js | 69 + .../playwright-core/lib/server/javascript.js | 291 + .../playwright-core/lib/server/launchApp.js | 128 + .../playwright-core/lib/server/localUtils.js | 214 + .../lib/server/macEditingCommands.js | 143 + .../playwright-core/lib/server/network.js | 629 + .../playwright-core/lib/server/page.js | 886 + .../lib/server/pipeTransport.js | 89 + .../playwright-core/lib/server/playwright.js | 69 + .../playwright-core/lib/server/progress.js | 112 + .../lib/server/protocolError.js | 52 + .../playwright-core/lib/server/recorder.js | 499 + .../lib/server/recorder/chat.js | 161 + .../lib/server/recorder/recorderApp.js | 387 + .../lib/server/recorder/recorderRunner.js | 138 + .../recorder/recorderSignalProcessor.js | 83 + .../lib/server/recorder/recorderUtils.js | 157 + .../lib/server/recorder/throttledFile.js | 57 + .../lib/server/registry/browserFetcher.js | 175 + .../lib/server/registry/dependencies.js | 371 + .../lib/server/registry/index.js | 1399 + .../lib/server/registry/nativeDeps.js | 1280 + .../server/registry/oopDownloadBrowserMain.js | 124 + .../lib/server/screenshotter.js | 333 + .../playwright-core/lib/server/selectors.js | 112 + .../socksClientCertificatesInterceptor.js | 383 + .../lib/server/socksInterceptor.js | 95 + .../lib/server/trace/recorder/snapshotter.js | 147 + .../trace/recorder/snapshotterInjected.js | 541 + .../lib/server/trace/recorder/tracing.js | 604 + .../server/trace/test/inMemorySnapshotter.js | 87 + .../lib/server/trace/viewer/traceViewer.js | 241 + .../playwright-core/lib/server/transport.js | 181 + .../playwright-core/lib/server/types.js | 28 + .../lib/server/usKeyboardLayout.js | 145 + .../playwright-core/lib/server/utils/ascii.js | 44 + .../lib/server/utils/comparators.js | 139 + .../lib/server/utils/crypto.js | 216 + .../playwright-core/lib/server/utils/debug.js | 42 + .../lib/server/utils/debugLogger.js | 122 + .../playwright-core/lib/server/utils/env.js | 73 + .../lib/server/utils/eventsHelper.js | 39 + .../lib/server/utils/expectUtils.js | 38 + .../lib/server/utils/fileUtils.js | 191 + .../lib/server/utils/happyEyeballs.js | 207 + .../lib/server/utils/hostPlatform.js | 123 + .../lib/server/utils/httpServer.js | 218 + .../lib/server/utils/imageUtils.js | 141 + .../server/utils/image_tools/colorUtils.js | 89 + .../lib/server/utils/image_tools/compare.js | 109 + .../server/utils/image_tools/imageChannel.js | 78 + .../lib/server/utils/image_tools/stats.js | 102 + .../lib/server/utils/linuxUtils.js | 71 + .../lib/server/utils/network.js | 233 + .../lib/server/utils/nodePlatform.js | 148 + .../lib/server/utils/pipeTransport.js | 84 + .../lib/server/utils/processLauncher.js | 241 + .../lib/server/utils/profiler.js | 65 + .../lib/server/utils/socksProxy.js | 511 + .../lib/server/utils/spawnAsync.js | 41 + .../playwright-core/lib/server/utils/task.js | 51 + .../lib/server/utils/userAgent.js | 98 + .../lib/server/utils/wsServer.js | 121 + .../lib/server/utils/zipFile.js | 74 + .../playwright-core/lib/server/utils/zones.js | 57 + .../lib/server/webkit/protocol.d.js | 16 + .../lib/server/webkit/webkit.js | 110 + .../lib/server/webkit/wkBrowser.js | 339 + .../lib/server/webkit/wkConnection.js | 149 + .../lib/server/webkit/wkExecutionContext.js | 154 + .../lib/server/webkit/wkInput.js | 181 + .../server/webkit/wkInterceptableRequest.js | 169 + .../lib/server/webkit/wkPage.js | 1130 + .../lib/server/webkit/wkProvisionalPage.js | 83 + .../lib/server/webkit/wkWorkers.js | 105 + .../lib/third_party/pixelmatch.js | 255 + .../node_modules/playwright-core/lib/utils.js | 109 + .../lib/utils/isomorphic/ariaSnapshot.js | 397 + .../lib/utils/isomorphic/assert.js | 31 + .../lib/utils/isomorphic/colors.js | 72 + .../lib/utils/isomorphic/cssParser.js | 245 + .../lib/utils/isomorphic/cssTokenizer.js | 1051 + .../lib/utils/isomorphic/headers.js | 53 + .../lib/utils/isomorphic/locatorGenerators.js | 689 + .../lib/utils/isomorphic/locatorParser.js | 176 + .../lib/utils/isomorphic/locatorUtils.js | 81 + .../lib/utils/isomorphic/manualPromise.js | 114 + .../lib/utils/isomorphic/mimeType.js | 459 + .../lib/utils/isomorphic/multimap.js | 80 + .../lib/utils/isomorphic/protocolFormatter.js | 81 + .../lib/utils/isomorphic/protocolMetainfo.js | 322 + .../lib/utils/isomorphic/rtti.js | 43 + .../lib/utils/isomorphic/selectorParser.js | 386 + .../lib/utils/isomorphic/semaphore.js | 54 + .../lib/utils/isomorphic/stackTrace.js | 158 + .../lib/utils/isomorphic/stringUtils.js | 155 + .../lib/utils/isomorphic/time.js | 49 + .../lib/utils/isomorphic/timeoutRunner.js | 66 + .../lib/utils/isomorphic/traceUtils.js | 58 + .../lib/utils/isomorphic/types.js | 16 + .../lib/utils/isomorphic/urlMatch.js | 190 + .../isomorphic/utilityScriptSerializers.js | 251 + .../playwright-core/lib/utilsBundle.js | 112 + .../lib/utilsBundleImpl/index.js | 218 + .../lib/utilsBundleImpl/xdg-open | 1066 + .../lib/vite/htmlReport/index.html | 84 + .../assets/codeMirrorModule-BoWUGj0J.js | 25 + .../assets/codeMirrorModule-C3UTv-Ge.css | 1 + .../vite/recorder/assets/codicon-DCmgc-ay.ttf | Bin 0 -> 80340 bytes .../vite/recorder/assets/index-DJqDAOZp.js | 193 + .../vite/recorder/assets/index-Ri0uHF7I.css | 1 + .../lib/vite/recorder/index.html | 29 + .../lib/vite/recorder/playwright-logo.svg | 9 + .../assets/codeMirrorModule-Bucv2d7q.js | 25 + .../assets/defaultSettingsView-BEpdCv1S.js | 266 + .../assets/xtermModule-CsJ4vdCR.js | 9 + .../traceViewer/codeMirrorModule.C3UTv-Ge.css | 1 + .../lib/vite/traceViewer/codicon.DCmgc-ay.ttf | Bin 0 -> 80340 bytes .../defaultSettingsView.ConWv5KN.css | 1 + .../lib/vite/traceViewer/index.BxQ34UMZ.js | 2 + .../lib/vite/traceViewer/index.C4Y3Aw8n.css | 1 + .../lib/vite/traceViewer/index.html | 43 + .../lib/vite/traceViewer/manifest.webmanifest | 16 + .../lib/vite/traceViewer/playwright-logo.svg | 9 + .../lib/vite/traceViewer/snapshot.html | 21 + .../lib/vite/traceViewer/sw.bundle.js | 3 + .../lib/vite/traceViewer/uiMode.BWTwXl41.js | 5 + .../lib/vite/traceViewer/uiMode.Btcz36p_.css | 1 + .../lib/vite/traceViewer/uiMode.html | 17 + .../vite/traceViewer/xtermModule.DYP7pi_n.css | 32 + .../playwright-core/lib/zipBundle.js | 34 + .../playwright-core/lib/zipBundleImpl.js | 5 + .../node_modules/playwright-core/package.json | 42 + .../playwright-core/types/protocol.d.ts | 23245 ++++++++++++++++ .../playwright-core/types/structs.d.ts | 45 + .../playwright-core/types/types.d.ts | 22856 +++++++++++++++ tests/node_modules/playwright/LICENSE | 202 + tests/node_modules/playwright/NOTICE | 5 + tests/node_modules/playwright/README.md | 168 + .../playwright/ThirdPartyNotices.txt | 6197 ++++ tests/node_modules/playwright/cli.js | 19 + tests/node_modules/playwright/index.d.ts | 17 + tests/node_modules/playwright/index.js | 17 + tests/node_modules/playwright/index.mjs | 18 + tests/node_modules/playwright/jsx-runtime.js | 42 + tests/node_modules/playwright/jsx-runtime.mjs | 21 + .../lib/agents/copilot-setup-steps.yml | 34 + .../playwright/lib/agents/generateAgents.js | 395 + .../agents/playwright-test-coverage.prompt.md | 31 + .../agents/playwright-test-generate.prompt.md | 8 + .../agents/playwright-test-generator.agent.md | 88 + .../lib/agents/playwright-test-heal.prompt.md | 6 + .../agents/playwright-test-healer.agent.md | 55 + .../lib/agents/playwright-test-plan.prompt.md | 9 + .../agents/playwright-test-planner.agent.md | 72 + .../playwright/lib/common/config.js | 280 + .../playwright/lib/common/configLoader.js | 344 + .../playwright/lib/common/esmLoaderHost.js | 102 + .../playwright/lib/common/expectBundle.js | 43 + .../playwright/lib/common/expectBundleImpl.js | 407 + .../playwright/lib/common/fixtures.js | 302 + .../playwright/lib/common/globals.js | 58 + .../node_modules/playwright/lib/common/ipc.js | 60 + .../playwright/lib/common/poolBuilder.js | 85 + .../playwright/lib/common/process.js | 104 + .../playwright/lib/common/suiteUtils.js | 140 + .../playwright/lib/common/test.js | 321 + .../playwright/lib/common/testLoader.js | 101 + .../playwright/lib/common/testType.js | 298 + .../playwright/lib/common/validators.js | 68 + .../node_modules/playwright/lib/fsWatcher.js | 67 + tests/node_modules/playwright/lib/index.js | 682 + .../playwright/lib/internalsForTest.js | 42 + .../playwright/lib/isomorphic/events.js | 77 + .../playwright/lib/isomorphic/folders.js | 30 + .../lib/isomorphic/stringInternPool.js | 69 + .../playwright/lib/isomorphic/teleReceiver.js | 508 + .../lib/isomorphic/teleSuiteUpdater.js | 137 + .../lib/isomorphic/testServerConnection.js | 225 + .../lib/isomorphic/testServerInterface.js | 16 + .../playwright/lib/isomorphic/testTree.js | 334 + .../playwright/lib/isomorphic/types.d.js | 16 + .../playwright/lib/loader/loaderMain.js | 59 + .../playwright/lib/matchers/expect.js | 324 + .../playwright/lib/matchers/matcherHint.js | 87 + .../playwright/lib/matchers/matchers.js | 382 + .../playwright/lib/matchers/toBeTruthy.js | 73 + .../playwright/lib/matchers/toEqual.js | 99 + .../playwright/lib/matchers/toHaveURL.js | 102 + .../lib/matchers/toMatchAriaSnapshot.js | 159 + .../lib/matchers/toMatchSnapshot.js | 341 + .../playwright/lib/matchers/toMatchText.js | 99 + .../playwright/lib/mcp/browser/actions.d.js | 16 + .../lib/mcp/browser/browserContextFactory.js | 296 + .../lib/mcp/browser/browserServerBackend.js | 76 + .../playwright/lib/mcp/browser/codegen.js | 66 + .../playwright/lib/mcp/browser/config.js | 368 + .../playwright/lib/mcp/browser/context.js | 267 + .../playwright/lib/mcp/browser/response.js | 237 + .../playwright/lib/mcp/browser/sessionLog.js | 160 + .../playwright/lib/mcp/browser/tab.js | 292 + .../playwright/lib/mcp/browser/tools.js | 82 + .../lib/mcp/browser/tools/common.js | 63 + .../lib/mcp/browser/tools/console.js | 44 + .../lib/mcp/browser/tools/dialogs.js | 60 + .../lib/mcp/browser/tools/evaluate.js | 69 + .../playwright/lib/mcp/browser/tools/files.js | 58 + .../playwright/lib/mcp/browser/tools/form.js | 73 + .../lib/mcp/browser/tools/install.js | 69 + .../lib/mcp/browser/tools/keyboard.js | 84 + .../playwright/lib/mcp/browser/tools/mouse.js | 107 + .../lib/mcp/browser/tools/navigate.js | 62 + .../lib/mcp/browser/tools/network.js | 54 + .../playwright/lib/mcp/browser/tools/pdf.js | 59 + .../lib/mcp/browser/tools/runCode.js | 75 + .../lib/mcp/browser/tools/screenshot.js | 106 + .../lib/mcp/browser/tools/snapshot.js | 181 + .../playwright/lib/mcp/browser/tools/tabs.js | 67 + .../playwright/lib/mcp/browser/tools/tool.js | 49 + .../lib/mcp/browser/tools/tracing.js | 74 + .../playwright/lib/mcp/browser/tools/utils.js | 89 + .../lib/mcp/browser/tools/verify.js | 153 + .../playwright/lib/mcp/browser/tools/wait.js | 63 + .../playwright/lib/mcp/browser/watchdog.js | 44 + .../playwright/lib/mcp/config.d.js | 16 + .../playwright/lib/mcp/extension/cdpRelay.js | 351 + .../mcp/extension/extensionContextFactory.js | 75 + .../playwright/lib/mcp/extension/protocol.js | 28 + .../node_modules/playwright/lib/mcp/index.js | 61 + tests/node_modules/playwright/lib/mcp/log.js | 35 + .../playwright/lib/mcp/program.js | 116 + .../playwright/lib/mcp/sdk/bundle.js | 81 + .../playwright/lib/mcp/sdk/exports.js | 30 + .../playwright/lib/mcp/sdk/http.js | 187 + .../lib/mcp/sdk/inProcessTransport.js | 71 + .../playwright/lib/mcp/sdk/proxyBackend.js | 128 + .../playwright/lib/mcp/sdk/server.js | 198 + .../playwright/lib/mcp/sdk/tool.js | 47 + .../playwright/lib/mcp/test/browserBackend.js | 108 + .../playwright/lib/mcp/test/generatorTools.js | 122 + .../playwright/lib/mcp/test/plannerTools.js | 144 + .../playwright/lib/mcp/test/seed.js | 82 + .../playwright/lib/mcp/test/streams.js | 44 + .../playwright/lib/mcp/test/testBackend.js | 99 + .../playwright/lib/mcp/test/testContext.js | 279 + .../playwright/lib/mcp/test/testTool.js | 30 + .../playwright/lib/mcp/test/testTools.js | 106 + .../playwright/lib/mcpBundleImpl.js | 41 + .../lib/plugins/gitCommitInfoPlugin.js | 198 + .../playwright/lib/plugins/index.js | 28 + .../playwright/lib/plugins/webServerPlugin.js | 237 + tests/node_modules/playwright/lib/program.js | 403 + .../playwright/lib/reporters/base.js | 609 + .../playwright/lib/reporters/blob.js | 135 + .../playwright/lib/reporters/dot.js | 82 + .../playwright/lib/reporters/empty.js | 32 + .../playwright/lib/reporters/github.js | 128 + .../playwright/lib/reporters/html.js | 623 + .../lib/reporters/internalReporter.js | 132 + .../playwright/lib/reporters/json.js | 254 + .../playwright/lib/reporters/junit.js | 232 + .../playwright/lib/reporters/line.js | 113 + .../playwright/lib/reporters/list.js | 231 + .../lib/reporters/listModeReporter.js | 69 + .../playwright/lib/reporters/markdown.js | 144 + .../playwright/lib/reporters/merge.js | 541 + .../playwright/lib/reporters/multiplexer.js | 104 + .../playwright/lib/reporters/reporterV2.js | 102 + .../playwright/lib/reporters/teleEmitter.js | 299 + .../lib/reporters/versions/blobV1.js | 16 + .../playwright/lib/runner/dispatcher.js | 515 + .../playwright/lib/runner/failureTracker.js | 72 + .../playwright/lib/runner/lastRun.js | 77 + .../playwright/lib/runner/loadUtils.js | 334 + .../playwright/lib/runner/loaderHost.js | 89 + .../playwright/lib/runner/processHost.js | 161 + .../playwright/lib/runner/projectUtils.js | 241 + .../playwright/lib/runner/rebase.js | 189 + .../playwright/lib/runner/reporters.js | 138 + .../playwright/lib/runner/sigIntWatcher.js | 96 + .../playwright/lib/runner/taskRunner.js | 127 + .../playwright/lib/runner/tasks.js | 410 + .../playwright/lib/runner/testGroups.js | 117 + .../playwright/lib/runner/testRunner.js | 389 + .../playwright/lib/runner/testServer.js | 269 + .../playwright/lib/runner/uiModeReporter.js | 30 + .../node_modules/playwright/lib/runner/vcs.js | 72 + .../playwright/lib/runner/watchMode.js | 395 + .../playwright/lib/runner/workerHost.js | 98 + .../playwright/lib/third_party/pirates.js | 62 + .../lib/third_party/tsconfig-loader.js | 103 + .../playwright/lib/transform/babelBundle.js | 43 + .../lib/transform/babelBundleImpl.js | 461 + .../lib/transform/compilationCache.js | 272 + .../playwright/lib/transform/esmLoader.js | 104 + .../playwright/lib/transform/portTransport.js | 67 + .../playwright/lib/transform/transform.js | 293 + tests/node_modules/playwright/lib/util.js | 403 + .../playwright/lib/utilsBundle.js | 43 + .../playwright/lib/utilsBundleImpl.js | 100 + .../playwright/lib/worker/fixtureRunner.js | 258 + .../playwright/lib/worker/testInfo.js | 516 + .../playwright/lib/worker/testTracing.js | 345 + .../playwright/lib/worker/timeoutManager.js | 174 + .../playwright/lib/worker/util.js | 31 + .../playwright/lib/worker/workerMain.js | 529 + tests/node_modules/playwright/package.json | 72 + tests/node_modules/playwright/test.d.ts | 18 + tests/node_modules/playwright/test.js | 24 + tests/node_modules/playwright/test.mjs | 33 + tests/node_modules/playwright/types/test.d.ts | 10253 +++++++ .../playwright/types/testReporter.d.ts | 821 + tests/package-lock.json | 78 + tests/package.json | 13 + web/package-lock.json | 64 + web/package.json | 2 +- 534 files changed, 154450 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/README.md create mode 100644 TESTING.md create mode 120000 tests/node_modules/.bin/playwright create mode 120000 tests/node_modules/.bin/playwright-core create mode 100644 tests/node_modules/.package-lock.json create mode 100644 tests/node_modules/@playwright/test/LICENSE create mode 100644 tests/node_modules/@playwright/test/NOTICE create mode 100644 tests/node_modules/@playwright/test/README.md create mode 100755 tests/node_modules/@playwright/test/cli.js create mode 100644 tests/node_modules/@playwright/test/index.d.ts create mode 100644 tests/node_modules/@playwright/test/index.js create mode 100644 tests/node_modules/@playwright/test/index.mjs create mode 100644 tests/node_modules/@playwright/test/package.json create mode 100644 tests/node_modules/@playwright/test/reporter.d.ts create mode 100644 tests/node_modules/@playwright/test/reporter.js create mode 100644 tests/node_modules/@playwright/test/reporter.mjs create mode 100644 tests/node_modules/playwright-core/LICENSE create mode 100644 tests/node_modules/playwright-core/NOTICE create mode 100644 tests/node_modules/playwright-core/README.md create mode 100644 tests/node_modules/playwright-core/ThirdPartyNotices.txt create mode 100644 tests/node_modules/playwright-core/bin/install_media_pack.ps1 create mode 100644 tests/node_modules/playwright-core/bin/install_webkit_wsl.ps1 create mode 100755 tests/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh create mode 100755 tests/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh create mode 100644 tests/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 create mode 100755 tests/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh create mode 100755 tests/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh create mode 100644 tests/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 create mode 100755 tests/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh create mode 100755 tests/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh create mode 100644 tests/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 create mode 100755 tests/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh create mode 100755 tests/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh create mode 100644 tests/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 create mode 100755 tests/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh create mode 100755 tests/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh create mode 100644 tests/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 create mode 100644 tests/node_modules/playwright-core/browsers.json create mode 100755 tests/node_modules/playwright-core/cli.js create mode 100644 tests/node_modules/playwright-core/index.d.ts create mode 100644 tests/node_modules/playwright-core/index.js create mode 100644 tests/node_modules/playwright-core/index.mjs create mode 100644 tests/node_modules/playwright-core/lib/androidServerImpl.js create mode 100644 tests/node_modules/playwright-core/lib/browserServerImpl.js create mode 100644 tests/node_modules/playwright-core/lib/cli/driver.js create mode 100644 tests/node_modules/playwright-core/lib/cli/program.js create mode 100644 tests/node_modules/playwright-core/lib/cli/programWithTestStub.js create mode 100644 tests/node_modules/playwright-core/lib/client/android.js create mode 100644 tests/node_modules/playwright-core/lib/client/api.js create mode 100644 tests/node_modules/playwright-core/lib/client/artifact.js create mode 100644 tests/node_modules/playwright-core/lib/client/browser.js create mode 100644 tests/node_modules/playwright-core/lib/client/browserContext.js create mode 100644 tests/node_modules/playwright-core/lib/client/browserType.js create mode 100644 tests/node_modules/playwright-core/lib/client/cdpSession.js create mode 100644 tests/node_modules/playwright-core/lib/client/channelOwner.js create mode 100644 tests/node_modules/playwright-core/lib/client/clientHelper.js create mode 100644 tests/node_modules/playwright-core/lib/client/clientInstrumentation.js create mode 100644 tests/node_modules/playwright-core/lib/client/clientStackTrace.js create mode 100644 tests/node_modules/playwright-core/lib/client/clock.js create mode 100644 tests/node_modules/playwright-core/lib/client/connection.js create mode 100644 tests/node_modules/playwright-core/lib/client/consoleMessage.js create mode 100644 tests/node_modules/playwright-core/lib/client/coverage.js create mode 100644 tests/node_modules/playwright-core/lib/client/dialog.js create mode 100644 tests/node_modules/playwright-core/lib/client/download.js create mode 100644 tests/node_modules/playwright-core/lib/client/electron.js create mode 100644 tests/node_modules/playwright-core/lib/client/elementHandle.js create mode 100644 tests/node_modules/playwright-core/lib/client/errors.js create mode 100644 tests/node_modules/playwright-core/lib/client/eventEmitter.js create mode 100644 tests/node_modules/playwright-core/lib/client/events.js create mode 100644 tests/node_modules/playwright-core/lib/client/fetch.js create mode 100644 tests/node_modules/playwright-core/lib/client/fileChooser.js create mode 100644 tests/node_modules/playwright-core/lib/client/fileUtils.js create mode 100644 tests/node_modules/playwright-core/lib/client/frame.js create mode 100644 tests/node_modules/playwright-core/lib/client/harRouter.js create mode 100644 tests/node_modules/playwright-core/lib/client/input.js create mode 100644 tests/node_modules/playwright-core/lib/client/jsHandle.js create mode 100644 tests/node_modules/playwright-core/lib/client/jsonPipe.js create mode 100644 tests/node_modules/playwright-core/lib/client/localUtils.js create mode 100644 tests/node_modules/playwright-core/lib/client/locator.js create mode 100644 tests/node_modules/playwright-core/lib/client/network.js create mode 100644 tests/node_modules/playwright-core/lib/client/page.js create mode 100644 tests/node_modules/playwright-core/lib/client/platform.js create mode 100644 tests/node_modules/playwright-core/lib/client/playwright.js create mode 100644 tests/node_modules/playwright-core/lib/client/selectors.js create mode 100644 tests/node_modules/playwright-core/lib/client/stream.js create mode 100644 tests/node_modules/playwright-core/lib/client/timeoutSettings.js create mode 100644 tests/node_modules/playwright-core/lib/client/tracing.js create mode 100644 tests/node_modules/playwright-core/lib/client/types.js create mode 100644 tests/node_modules/playwright-core/lib/client/video.js create mode 100644 tests/node_modules/playwright-core/lib/client/waiter.js create mode 100644 tests/node_modules/playwright-core/lib/client/webError.js create mode 100644 tests/node_modules/playwright-core/lib/client/webSocket.js create mode 100644 tests/node_modules/playwright-core/lib/client/worker.js create mode 100644 tests/node_modules/playwright-core/lib/client/writableStream.js create mode 100644 tests/node_modules/playwright-core/lib/generated/bindingsControllerSource.js create mode 100644 tests/node_modules/playwright-core/lib/generated/clockSource.js create mode 100644 tests/node_modules/playwright-core/lib/generated/injectedScriptSource.js create mode 100644 tests/node_modules/playwright-core/lib/generated/pollingRecorderSource.js create mode 100644 tests/node_modules/playwright-core/lib/generated/storageScriptSource.js create mode 100644 tests/node_modules/playwright-core/lib/generated/utilityScriptSource.js create mode 100644 tests/node_modules/playwright-core/lib/generated/webSocketMockSource.js create mode 100644 tests/node_modules/playwright-core/lib/inProcessFactory.js create mode 100644 tests/node_modules/playwright-core/lib/inprocess.js create mode 100644 tests/node_modules/playwright-core/lib/outofprocess.js create mode 100644 tests/node_modules/playwright-core/lib/protocol/serializers.js create mode 100644 tests/node_modules/playwright-core/lib/protocol/validator.js create mode 100644 tests/node_modules/playwright-core/lib/protocol/validatorPrimitives.js create mode 100644 tests/node_modules/playwright-core/lib/remote/playwrightConnection.js create mode 100644 tests/node_modules/playwright-core/lib/remote/playwrightServer.js create mode 100644 tests/node_modules/playwright-core/lib/server/android/android.js create mode 100644 tests/node_modules/playwright-core/lib/server/android/backendAdb.js create mode 100644 tests/node_modules/playwright-core/lib/server/artifact.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiBrowser.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiChromium.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiConnection.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiExecutionContext.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiFirefox.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiInput.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiNetworkManager.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiOverCdp.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiPage.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/bidiPdf.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiCommands.d.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiDeserializer.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiKeyboard.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiProtocol.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiProtocolCore.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiProtocolPermissions.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/bidiSerializer.js create mode 100644 tests/node_modules/playwright-core/lib/server/bidi/third_party/firefoxPrefs.js create mode 100644 tests/node_modules/playwright-core/lib/server/browser.js create mode 100644 tests/node_modules/playwright-core/lib/server/browserContext.js create mode 100644 tests/node_modules/playwright-core/lib/server/browserType.js create mode 100644 tests/node_modules/playwright-core/lib/server/callLog.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/appIcon.png create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/chromium.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/chromiumSwitches.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crBrowser.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crConnection.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crCoverage.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crDevTools.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crDragDrop.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crExecutionContext.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crInput.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crNetworkManager.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crPage.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crPdf.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crProtocolHelper.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/crServiceWorker.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/defaultFontFamilies.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/protocol.d.js create mode 100644 tests/node_modules/playwright-core/lib/server/chromium/videoRecorder.js create mode 100644 tests/node_modules/playwright-core/lib/server/clock.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/csharp.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/java.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/javascript.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/jsonl.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/language.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/languages.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/python.js create mode 100644 tests/node_modules/playwright-core/lib/server/codegen/types.js create mode 100644 tests/node_modules/playwright-core/lib/server/console.js create mode 100644 tests/node_modules/playwright-core/lib/server/cookieStore.js create mode 100644 tests/node_modules/playwright-core/lib/server/debugController.js create mode 100644 tests/node_modules/playwright-core/lib/server/debugger.js create mode 100644 tests/node_modules/playwright-core/lib/server/deviceDescriptors.js create mode 100644 tests/node_modules/playwright-core/lib/server/deviceDescriptorsSource.json create mode 100644 tests/node_modules/playwright-core/lib/server/dialog.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/androidDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/artifactDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/browserContextDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/browserDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/browserTypeDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/cdpSessionDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/debugControllerDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/dialogDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/dispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/electronDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/elementHandlerDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/frameDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/jsHandleDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/jsonPipeDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/localUtilsDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/networkDispatchers.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/pageDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/playwrightDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/streamDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/tracingDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/webSocketRouteDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dispatchers/writableStreamDispatcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/dom.js create mode 100644 tests/node_modules/playwright-core/lib/server/download.js create mode 100644 tests/node_modules/playwright-core/lib/server/electron/electron.js create mode 100644 tests/node_modules/playwright-core/lib/server/electron/loader.js create mode 100644 tests/node_modules/playwright-core/lib/server/errors.js create mode 100644 tests/node_modules/playwright-core/lib/server/fetch.js create mode 100644 tests/node_modules/playwright-core/lib/server/fileChooser.js create mode 100644 tests/node_modules/playwright-core/lib/server/fileUploadUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/ffBrowser.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/ffConnection.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/ffExecutionContext.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/ffInput.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/ffNetworkManager.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/ffPage.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/firefox.js create mode 100644 tests/node_modules/playwright-core/lib/server/firefox/protocol.d.js create mode 100644 tests/node_modules/playwright-core/lib/server/formData.js create mode 100644 tests/node_modules/playwright-core/lib/server/frameSelectors.js create mode 100644 tests/node_modules/playwright-core/lib/server/frames.js create mode 100644 tests/node_modules/playwright-core/lib/server/har/harRecorder.js create mode 100644 tests/node_modules/playwright-core/lib/server/har/harTracer.js create mode 100644 tests/node_modules/playwright-core/lib/server/harBackend.js create mode 100644 tests/node_modules/playwright-core/lib/server/helper.js create mode 100644 tests/node_modules/playwright-core/lib/server/index.js create mode 100644 tests/node_modules/playwright-core/lib/server/input.js create mode 100644 tests/node_modules/playwright-core/lib/server/instrumentation.js create mode 100644 tests/node_modules/playwright-core/lib/server/javascript.js create mode 100644 tests/node_modules/playwright-core/lib/server/launchApp.js create mode 100644 tests/node_modules/playwright-core/lib/server/localUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/macEditingCommands.js create mode 100644 tests/node_modules/playwright-core/lib/server/network.js create mode 100644 tests/node_modules/playwright-core/lib/server/page.js create mode 100644 tests/node_modules/playwright-core/lib/server/pipeTransport.js create mode 100644 tests/node_modules/playwright-core/lib/server/playwright.js create mode 100644 tests/node_modules/playwright-core/lib/server/progress.js create mode 100644 tests/node_modules/playwright-core/lib/server/protocolError.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder/chat.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder/recorderApp.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder/recorderRunner.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder/recorderUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/recorder/throttledFile.js create mode 100644 tests/node_modules/playwright-core/lib/server/registry/browserFetcher.js create mode 100644 tests/node_modules/playwright-core/lib/server/registry/dependencies.js create mode 100644 tests/node_modules/playwright-core/lib/server/registry/index.js create mode 100644 tests/node_modules/playwright-core/lib/server/registry/nativeDeps.js create mode 100644 tests/node_modules/playwright-core/lib/server/registry/oopDownloadBrowserMain.js create mode 100644 tests/node_modules/playwright-core/lib/server/screenshotter.js create mode 100644 tests/node_modules/playwright-core/lib/server/selectors.js create mode 100644 tests/node_modules/playwright-core/lib/server/socksClientCertificatesInterceptor.js create mode 100644 tests/node_modules/playwright-core/lib/server/socksInterceptor.js create mode 100644 tests/node_modules/playwright-core/lib/server/trace/recorder/snapshotter.js create mode 100644 tests/node_modules/playwright-core/lib/server/trace/recorder/snapshotterInjected.js create mode 100644 tests/node_modules/playwright-core/lib/server/trace/recorder/tracing.js create mode 100644 tests/node_modules/playwright-core/lib/server/trace/test/inMemorySnapshotter.js create mode 100644 tests/node_modules/playwright-core/lib/server/trace/viewer/traceViewer.js create mode 100644 tests/node_modules/playwright-core/lib/server/transport.js create mode 100644 tests/node_modules/playwright-core/lib/server/types.js create mode 100644 tests/node_modules/playwright-core/lib/server/usKeyboardLayout.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/ascii.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/comparators.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/crypto.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/debug.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/debugLogger.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/env.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/eventsHelper.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/expectUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/fileUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/happyEyeballs.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/hostPlatform.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/httpServer.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/imageUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/image_tools/colorUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/image_tools/compare.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/image_tools/imageChannel.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/image_tools/stats.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/linuxUtils.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/network.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/nodePlatform.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/pipeTransport.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/processLauncher.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/profiler.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/socksProxy.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/spawnAsync.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/task.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/userAgent.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/wsServer.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/zipFile.js create mode 100644 tests/node_modules/playwright-core/lib/server/utils/zones.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/protocol.d.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/webkit.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkBrowser.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkConnection.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkExecutionContext.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkInput.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkInterceptableRequest.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkPage.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkProvisionalPage.js create mode 100644 tests/node_modules/playwright-core/lib/server/webkit/wkWorkers.js create mode 100644 tests/node_modules/playwright-core/lib/third_party/pixelmatch.js create mode 100644 tests/node_modules/playwright-core/lib/utils.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/ariaSnapshot.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/assert.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/colors.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/cssParser.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/cssTokenizer.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/headers.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/locatorGenerators.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/locatorParser.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/locatorUtils.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/manualPromise.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/mimeType.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/multimap.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/protocolFormatter.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/protocolMetainfo.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/rtti.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/selectorParser.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/semaphore.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/stackTrace.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/stringUtils.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/time.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/timeoutRunner.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/traceUtils.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/types.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/urlMatch.js create mode 100644 tests/node_modules/playwright-core/lib/utils/isomorphic/utilityScriptSerializers.js create mode 100644 tests/node_modules/playwright-core/lib/utilsBundle.js create mode 100644 tests/node_modules/playwright-core/lib/utilsBundleImpl/index.js create mode 100755 tests/node_modules/playwright-core/lib/utilsBundleImpl/xdg-open create mode 100644 tests/node_modules/playwright-core/lib/vite/htmlReport/index.html create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-BoWUGj0J.js create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-C3UTv-Ge.css create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/assets/index-DJqDAOZp.js create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/assets/index-Ri0uHF7I.css create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/index.html create mode 100644 tests/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-Bucv2d7q.js create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-BEpdCv1S.js create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.C3UTv-Ge.css create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.ConWv5KN.css create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/index.BxQ34UMZ.js create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/index.C4Y3Aw8n.css create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/index.html create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/uiMode.BWTwXl41.js create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Btcz36p_.css create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html create mode 100644 tests/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.DYP7pi_n.css create mode 100644 tests/node_modules/playwright-core/lib/zipBundle.js create mode 100644 tests/node_modules/playwright-core/lib/zipBundleImpl.js create mode 100644 tests/node_modules/playwright-core/package.json create mode 100644 tests/node_modules/playwright-core/types/protocol.d.ts create mode 100644 tests/node_modules/playwright-core/types/structs.d.ts create mode 100644 tests/node_modules/playwright-core/types/types.d.ts create mode 100644 tests/node_modules/playwright/LICENSE create mode 100644 tests/node_modules/playwright/NOTICE create mode 100644 tests/node_modules/playwright/README.md create mode 100644 tests/node_modules/playwright/ThirdPartyNotices.txt create mode 100755 tests/node_modules/playwright/cli.js create mode 100644 tests/node_modules/playwright/index.d.ts create mode 100644 tests/node_modules/playwright/index.js create mode 100644 tests/node_modules/playwright/index.mjs create mode 100644 tests/node_modules/playwright/jsx-runtime.js create mode 100644 tests/node_modules/playwright/jsx-runtime.mjs create mode 100644 tests/node_modules/playwright/lib/agents/copilot-setup-steps.yml create mode 100644 tests/node_modules/playwright/lib/agents/generateAgents.js create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-coverage.prompt.md create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-generate.prompt.md create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-generator.agent.md create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-heal.prompt.md create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-healer.agent.md create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-plan.prompt.md create mode 100644 tests/node_modules/playwright/lib/agents/playwright-test-planner.agent.md create mode 100644 tests/node_modules/playwright/lib/common/config.js create mode 100644 tests/node_modules/playwright/lib/common/configLoader.js create mode 100644 tests/node_modules/playwright/lib/common/esmLoaderHost.js create mode 100644 tests/node_modules/playwright/lib/common/expectBundle.js create mode 100644 tests/node_modules/playwright/lib/common/expectBundleImpl.js create mode 100644 tests/node_modules/playwright/lib/common/fixtures.js create mode 100644 tests/node_modules/playwright/lib/common/globals.js create mode 100644 tests/node_modules/playwright/lib/common/ipc.js create mode 100644 tests/node_modules/playwright/lib/common/poolBuilder.js create mode 100644 tests/node_modules/playwright/lib/common/process.js create mode 100644 tests/node_modules/playwright/lib/common/suiteUtils.js create mode 100644 tests/node_modules/playwright/lib/common/test.js create mode 100644 tests/node_modules/playwright/lib/common/testLoader.js create mode 100644 tests/node_modules/playwright/lib/common/testType.js create mode 100644 tests/node_modules/playwright/lib/common/validators.js create mode 100644 tests/node_modules/playwright/lib/fsWatcher.js create mode 100644 tests/node_modules/playwright/lib/index.js create mode 100644 tests/node_modules/playwright/lib/internalsForTest.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/events.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/folders.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/stringInternPool.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/teleReceiver.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/teleSuiteUpdater.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/testServerConnection.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/testServerInterface.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/testTree.js create mode 100644 tests/node_modules/playwright/lib/isomorphic/types.d.js create mode 100644 tests/node_modules/playwright/lib/loader/loaderMain.js create mode 100644 tests/node_modules/playwright/lib/matchers/expect.js create mode 100644 tests/node_modules/playwright/lib/matchers/matcherHint.js create mode 100644 tests/node_modules/playwright/lib/matchers/matchers.js create mode 100644 tests/node_modules/playwright/lib/matchers/toBeTruthy.js create mode 100644 tests/node_modules/playwright/lib/matchers/toEqual.js create mode 100644 tests/node_modules/playwright/lib/matchers/toHaveURL.js create mode 100644 tests/node_modules/playwright/lib/matchers/toMatchAriaSnapshot.js create mode 100644 tests/node_modules/playwright/lib/matchers/toMatchSnapshot.js create mode 100644 tests/node_modules/playwright/lib/matchers/toMatchText.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/actions.d.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/browserContextFactory.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/browserServerBackend.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/codegen.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/config.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/context.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/response.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/sessionLog.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tab.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/common.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/console.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/dialogs.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/evaluate.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/files.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/form.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/install.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/keyboard.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/mouse.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/navigate.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/network.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/pdf.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/runCode.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/screenshot.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/snapshot.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/tabs.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/tool.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/tracing.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/utils.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/verify.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/tools/wait.js create mode 100644 tests/node_modules/playwright/lib/mcp/browser/watchdog.js create mode 100644 tests/node_modules/playwright/lib/mcp/config.d.js create mode 100644 tests/node_modules/playwright/lib/mcp/extension/cdpRelay.js create mode 100644 tests/node_modules/playwright/lib/mcp/extension/extensionContextFactory.js create mode 100644 tests/node_modules/playwright/lib/mcp/extension/protocol.js create mode 100644 tests/node_modules/playwright/lib/mcp/index.js create mode 100644 tests/node_modules/playwright/lib/mcp/log.js create mode 100644 tests/node_modules/playwright/lib/mcp/program.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/bundle.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/exports.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/http.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/inProcessTransport.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/proxyBackend.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/server.js create mode 100644 tests/node_modules/playwright/lib/mcp/sdk/tool.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/browserBackend.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/generatorTools.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/plannerTools.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/seed.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/streams.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/testBackend.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/testContext.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/testTool.js create mode 100644 tests/node_modules/playwright/lib/mcp/test/testTools.js create mode 100644 tests/node_modules/playwright/lib/mcpBundleImpl.js create mode 100644 tests/node_modules/playwright/lib/plugins/gitCommitInfoPlugin.js create mode 100644 tests/node_modules/playwright/lib/plugins/index.js create mode 100644 tests/node_modules/playwright/lib/plugins/webServerPlugin.js create mode 100644 tests/node_modules/playwright/lib/program.js create mode 100644 tests/node_modules/playwright/lib/reporters/base.js create mode 100644 tests/node_modules/playwright/lib/reporters/blob.js create mode 100644 tests/node_modules/playwright/lib/reporters/dot.js create mode 100644 tests/node_modules/playwright/lib/reporters/empty.js create mode 100644 tests/node_modules/playwright/lib/reporters/github.js create mode 100644 tests/node_modules/playwright/lib/reporters/html.js create mode 100644 tests/node_modules/playwright/lib/reporters/internalReporter.js create mode 100644 tests/node_modules/playwright/lib/reporters/json.js create mode 100644 tests/node_modules/playwright/lib/reporters/junit.js create mode 100644 tests/node_modules/playwright/lib/reporters/line.js create mode 100644 tests/node_modules/playwright/lib/reporters/list.js create mode 100644 tests/node_modules/playwright/lib/reporters/listModeReporter.js create mode 100644 tests/node_modules/playwright/lib/reporters/markdown.js create mode 100644 tests/node_modules/playwright/lib/reporters/merge.js create mode 100644 tests/node_modules/playwright/lib/reporters/multiplexer.js create mode 100644 tests/node_modules/playwright/lib/reporters/reporterV2.js create mode 100644 tests/node_modules/playwright/lib/reporters/teleEmitter.js create mode 100644 tests/node_modules/playwright/lib/reporters/versions/blobV1.js create mode 100644 tests/node_modules/playwright/lib/runner/dispatcher.js create mode 100644 tests/node_modules/playwright/lib/runner/failureTracker.js create mode 100644 tests/node_modules/playwright/lib/runner/lastRun.js create mode 100644 tests/node_modules/playwright/lib/runner/loadUtils.js create mode 100644 tests/node_modules/playwright/lib/runner/loaderHost.js create mode 100644 tests/node_modules/playwright/lib/runner/processHost.js create mode 100644 tests/node_modules/playwright/lib/runner/projectUtils.js create mode 100644 tests/node_modules/playwright/lib/runner/rebase.js create mode 100644 tests/node_modules/playwright/lib/runner/reporters.js create mode 100644 tests/node_modules/playwright/lib/runner/sigIntWatcher.js create mode 100644 tests/node_modules/playwright/lib/runner/taskRunner.js create mode 100644 tests/node_modules/playwright/lib/runner/tasks.js create mode 100644 tests/node_modules/playwright/lib/runner/testGroups.js create mode 100644 tests/node_modules/playwright/lib/runner/testRunner.js create mode 100644 tests/node_modules/playwright/lib/runner/testServer.js create mode 100644 tests/node_modules/playwright/lib/runner/uiModeReporter.js create mode 100644 tests/node_modules/playwright/lib/runner/vcs.js create mode 100644 tests/node_modules/playwright/lib/runner/watchMode.js create mode 100644 tests/node_modules/playwright/lib/runner/workerHost.js create mode 100644 tests/node_modules/playwright/lib/third_party/pirates.js create mode 100644 tests/node_modules/playwright/lib/third_party/tsconfig-loader.js create mode 100644 tests/node_modules/playwright/lib/transform/babelBundle.js create mode 100644 tests/node_modules/playwright/lib/transform/babelBundleImpl.js create mode 100644 tests/node_modules/playwright/lib/transform/compilationCache.js create mode 100644 tests/node_modules/playwright/lib/transform/esmLoader.js create mode 100644 tests/node_modules/playwright/lib/transform/portTransport.js create mode 100644 tests/node_modules/playwright/lib/transform/transform.js create mode 100644 tests/node_modules/playwright/lib/util.js create mode 100644 tests/node_modules/playwright/lib/utilsBundle.js create mode 100644 tests/node_modules/playwright/lib/utilsBundleImpl.js create mode 100644 tests/node_modules/playwright/lib/worker/fixtureRunner.js create mode 100644 tests/node_modules/playwright/lib/worker/testInfo.js create mode 100644 tests/node_modules/playwright/lib/worker/testTracing.js create mode 100644 tests/node_modules/playwright/lib/worker/timeoutManager.js create mode 100644 tests/node_modules/playwright/lib/worker/util.js create mode 100644 tests/node_modules/playwright/lib/worker/workerMain.js create mode 100644 tests/node_modules/playwright/package.json create mode 100644 tests/node_modules/playwright/test.d.ts create mode 100644 tests/node_modules/playwright/test.js create mode 100644 tests/node_modules/playwright/test.mjs create mode 100644 tests/node_modules/playwright/types/test.d.ts create mode 100644 tests/node_modules/playwright/types/testReporter.d.ts create mode 100644 tests/package-lock.json create mode 100644 tests/package.json diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..eb8ec126 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,69 @@ +# Running GitHub Actions Locally + +This project uses GitHub Actions for CI/CD. You can run these workflows locally using [act](https://github.com/nektos/act), which simulates GitHub Actions in a Docker environment. + +## Prerequisites + +1. **Docker**: Ensure Docker is installed and running + ```bash + docker --version + ``` + +2. **Install act**: + - **macOS**: `brew install act` + - **Linux**: Download from [act releases](https://github.com/nektos/act/releases) + - **Windows**: Use WSL or download from releases + +## Usage + +### List all workflows +```bash +act -l +``` + +### Run all workflows +```bash +act +``` + +### Run a specific workflow +```bash +act -W .github/workflows/tests.yml +``` + +### Run a specific job +```bash +act -j backend-tests +act -j frontend-tests +act -j e2e-tests +``` + +### Run on specific event +```bash +act push +act pull_request +``` + +### Use secrets (if needed) +Create a `.secrets` file in the repository root: +``` +SECRET_NAME=secret_value +``` + +Then run: +```bash +act --secret-file .secrets +``` + +## Limitations + +- Services (PostgreSQL, Redis) are automatically set up by act +- Some actions may behave differently locally vs. on GitHub +- Large workflows may take longer locally + +## Troubleshooting + +- If Docker images fail to pull, use `act --pull=false` +- For verbose output: `act -v` +- To use a specific platform: `act --container-architecture linux/amd64` + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a19bfac4..aa07e0cc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -155,9 +155,14 @@ jobs: working-directory: web run: npm ci + - name: Install E2E test dependencies + working-directory: tests + run: npm ci + - name: Install Playwright browsers working-directory: tests run: npx playwright install --with-deps chromium + continue-on-error: true - name: Run E2E tests working-directory: tests diff --git a/README.md b/README.md index c37331f4..869cb672 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,47 @@ Once the development environment is running: - **keycloak/** - Keycloak realm configuration - **scaffold/** - Initial data for hospital structure +## Testing + +### Running Tests Locally + +**Backend Tests:** +```bash +cd backend +python -m pytest tests/unit -v +python -m pytest tests/integration -v +``` + +**Frontend Linting:** +```bash +cd web +npm run lint +``` + +**E2E Tests:** +```bash +cd tests +npm install +npx playwright test +``` + +### Running GitHub Actions Locally + +You can run GitHub Actions workflows locally using [act](https://github.com/nektos/act). See [.github/workflows/README.md](.github/workflows/README.md) for detailed instructions. + +Quick start: +```bash +# Install act (requires Docker) +brew install act # macOS +# or download from https://github.com/nektos/act/releases + +# Run all workflows +act + +# Run specific job +act -j backend-tests +``` + ## Docker Images All components are containerized and available on GitHub Container Registry: diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000..254a1feb --- /dev/null +++ b/TESTING.md @@ -0,0 +1,153 @@ +# Testing Guide + +This document describes how to run tests for all components of the tasks project. + +## Quick Test Commands + +### Backend Tests +```bash +cd backend +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +pip install -r requirements.txt +pytest tests/unit -v +pytest tests/integration -v +pytest tests/ -v # Run all tests +``` + +### Frontend Linting +```bash +cd web +npm install +npm run lint +``` + +### E2E Tests +```bash +cd tests +npm install +npx playwright install chromium +npx playwright test +``` + +### All Linting +```bash +# Backend +cd backend +ruff check . + +# Simulator +cd simulator +ruff check . + +# Frontend +cd web +npm run lint +``` + +## Running GitHub Actions Locally + +Use [act](https://github.com/nektos/act) to run GitHub Actions workflows locally. + +### Installation + +**macOS:** +```bash +brew install act +``` + +**Linux:** +```bash +# Download from https://github.com/nektos/act/releases +# Or use package manager if available +``` + +**Prerequisites:** +- Docker must be installed and running + +### Usage + +```bash +# List all workflows +act -l + +# Run all workflows +act + +# Run specific workflow file +act -W .github/workflows/tests.yml + +# Run specific job +act -j backend-tests +act -j frontend-tests +act -j e2e-tests + +# Run on specific event +act push +act pull_request +``` + +### Troubleshooting + +- If Docker images fail: `act --pull=false` +- For verbose output: `act -v` +- To use secrets: Create `.secrets` file and use `act --secret-file .secrets` + +See [.github/workflows/README.md](.github/workflows/README.md) for more details. + +## CI/CD Pipeline + +The GitHub Actions workflow (`.github/workflows/tests.yml`) runs: + +1. **Backend Tests** - Unit and integration tests across Python 3.11, 3.12, 3.13 +2. **Frontend Tests** - TypeScript type checking and ESLint +3. **E2E Tests** - Playwright end-to-end tests + +All tests run automatically on: +- Push to `main` or `develop` branches +- Pull requests to `main` or `develop` branches + +## Test Structure + +``` +backend/ + tests/ + unit/ # Unit tests for services and utilities + integration/ # Integration tests for resolvers + conftest.py # Shared test fixtures + +tests/ + e2e/ # End-to-end Playwright tests + *.spec.ts # Test specifications + playwright.config.ts + package.json # E2E test dependencies +``` + +## Fixing Common Issues + +### npm ci Error (EUSAGE) + +If you see `npm ci` errors about lock file sync: +```bash +cd web # or tests +rm -rf node_modules package-lock.json +npm install +``` + +### Playwright Browser Not Found + +```bash +cd tests +npx playwright install chromium +``` + +### Python Module Not Found + +Ensure you're in a virtual environment with dependencies installed: +```bash +cd backend +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + diff --git a/backend/api/context.py b/backend/api/context.py index 7a6206a4..b736cc4a 100644 --- a/backend/api/context.py +++ b/backend/api/context.py @@ -39,7 +39,6 @@ async def get_context( email = user_payload.get("email") picture = user_payload.get("picture") - # Extract organizations from OIDC token (can be array or single value) organizations_raw = user_payload.get("organization") organizations = None if organizations_raw: diff --git a/tests/node_modules/.bin/playwright b/tests/node_modules/.bin/playwright new file mode 120000 index 00000000..c30d07fa --- /dev/null +++ b/tests/node_modules/.bin/playwright @@ -0,0 +1 @@ +../@playwright/test/cli.js \ No newline at end of file diff --git a/tests/node_modules/.bin/playwright-core b/tests/node_modules/.bin/playwright-core new file mode 120000 index 00000000..08d6c281 --- /dev/null +++ b/tests/node_modules/.bin/playwright-core @@ -0,0 +1 @@ +../playwright-core/cli.js \ No newline at end of file diff --git a/tests/node_modules/.package-lock.json b/tests/node_modules/.package-lock.json new file mode 100644 index 00000000..8021ed58 --- /dev/null +++ b/tests/node_modules/.package-lock.json @@ -0,0 +1,56 @@ +{ + "name": "tasks-e2e-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/tests/node_modules/@playwright/test/LICENSE b/tests/node_modules/@playwright/test/LICENSE new file mode 100644 index 00000000..df112373 --- /dev/null +++ b/tests/node_modules/@playwright/test/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tests/node_modules/@playwright/test/NOTICE b/tests/node_modules/@playwright/test/NOTICE new file mode 100644 index 00000000..814ec169 --- /dev/null +++ b/tests/node_modules/@playwright/test/NOTICE @@ -0,0 +1,5 @@ +Playwright +Copyright (c) Microsoft Corporation + +This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer), +available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE). diff --git a/tests/node_modules/@playwright/test/README.md b/tests/node_modules/@playwright/test/README.md new file mode 100644 index 00000000..c3a59740 --- /dev/null +++ b/tests/node_modules/@playwright/test/README.md @@ -0,0 +1,168 @@ +# 🎭 Playwright + +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-143.0.7499.4-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-144.0.2-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-26.0-blue.svg?logo=safari)](https://webkit.org/) [![Join Discord](https://img.shields.io/badge/join-discord-informational)](https://aka.ms/playwright/discord) + +## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) + +Playwright is a framework for Web Testing and Automation. It allows testing [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**. + +| | Linux | macOS | Windows | +| :--- | :---: | :---: | :---: | +| Chromium 143.0.7499.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| WebKit 26.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 144.0.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | + +Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details. + +Looking for Playwright for [Python](https://playwright.dev/python/docs/intro), [.NET](https://playwright.dev/dotnet/docs/intro), or [Java](https://playwright.dev/java/docs/intro)? + +## Installation + +Playwright has its own test runner for end-to-end tests, we call it Playwright Test. + +### Using init command + +The easiest way to get started with Playwright Test is to run the init command. + +```Shell +# Run from your project's root directory +npm init playwright@latest +# Or create a new project +npm init playwright@latest new-project +``` + +This will create a configuration file, optionally add examples, a GitHub Action workflow and a first test example.spec.ts. You can now jump directly to writing assertions section. + +### Manually + +Add dependency and install browsers. + +```Shell +npm i -D @playwright/test +# install supported browsers +npx playwright install +``` + +You can optionally install only selected browsers, see [install browsers](https://playwright.dev/docs/cli#install-browsers) for more details. Or you can install no browsers at all and use existing [browser channels](https://playwright.dev/docs/browsers). + +* [Getting started](https://playwright.dev/docs/intro) +* [API reference](https://playwright.dev/docs/api/class-playwright) + +## Capabilities + +### Resilient • No flaky tests + +**Auto-wait**. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - a primary cause of flaky tests. + +**Web-first assertions**. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met. + +**Tracing**. Configure test retry strategy, capture execution trace, videos and screenshots to eliminate flakes. + +### No trade-offs • No limits + +Browsers run web content belonging to different origins in different processes. Playwright is aligned with the architecture of the modern browsers and runs tests out-of-process. This makes Playwright free of the typical in-process test runner limitations. + +**Multiple everything**. Test scenarios that span multiple tabs, multiple origins and multiple users. Create scenarios with different contexts for different users and run them against your server, all in one test. + +**Trusted events**. Hover elements, interact with dynamic controls and produce trusted events. Playwright uses real browser input pipeline indistinguishable from the real user. + +Test frames, pierce Shadow DOM. Playwright selectors pierce shadow DOM and allow entering frames seamlessly. + +### Full isolation • Fast execution + +**Browser contexts**. Playwright creates a browser context for each test. Browser context is equivalent to a brand new browser profile. This delivers full test isolation with zero overhead. Creating a new browser context only takes a handful of milliseconds. + +**Log in once**. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests. + +### Powerful Tooling + +**[Codegen](https://playwright.dev/docs/codegen)**. Generate tests by recording your actions. Save them into any language. + +**[Playwright inspector](https://playwright.dev/docs/inspector)**. Inspect page, generate selectors, step through the test execution, see click points and explore execution logs. + +**[Trace Viewer](https://playwright.dev/docs/trace-viewer)**. Capture all the information to investigate the test failure. Playwright trace contains test execution screencast, live DOM snapshots, action explorer, test source and many more. + +Looking for Playwright for [TypeScript](https://playwright.dev/docs/intro), [JavaScript](https://playwright.dev/docs/intro), [Python](https://playwright.dev/python/docs/intro), [.NET](https://playwright.dev/dotnet/docs/intro), or [Java](https://playwright.dev/java/docs/intro)? + +## Examples + +To learn how to run these Playwright Test examples, check out our [getting started docs](https://playwright.dev/docs/intro). + +#### Page screenshot + +This code snippet navigates to Playwright homepage and saves a screenshot. + +```TypeScript +import { test } from '@playwright/test'; + +test('Page Screenshot', async ({ page }) => { + await page.goto('https://playwright.dev/'); + await page.screenshot({ path: `example.png` }); +}); +``` + +#### Mobile and geolocation + +This snippet emulates Mobile Safari on a device at given geolocation, navigates to maps.google.com, performs the action and takes a screenshot. + +```TypeScript +import { test, devices } from '@playwright/test'; + +test.use({ + ...devices['iPhone 13 Pro'], + locale: 'en-US', + geolocation: { longitude: 12.492507, latitude: 41.889938 }, + permissions: ['geolocation'], +}) + +test('Mobile and geolocation', async ({ page }) => { + await page.goto('https://maps.google.com'); + await page.getByText('Your location').click(); + await page.waitForRequest(/.*preview\/pwa/); + await page.screenshot({ path: 'colosseum-iphone.png' }); +}); +``` + +#### Evaluate in browser context + +This code snippet navigates to example.com, and executes a script in the page context. + +```TypeScript +import { test } from '@playwright/test'; + +test('Evaluate in browser context', async ({ page }) => { + await page.goto('https://www.example.com/'); + const dimensions = await page.evaluate(() => { + return { + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, + deviceScaleFactor: window.devicePixelRatio + } + }); + console.log(dimensions); +}); +``` + +#### Intercept network requests + +This code snippet sets up request routing for a page to log all network requests. + +```TypeScript +import { test } from '@playwright/test'; + +test('Intercept network requests', async ({ page }) => { + // Log and continue all network requests + await page.route('**', route => { + console.log(route.request().url()); + route.continue(); + }); + await page.goto('http://todomvc.com'); +}); +``` + +## Resources + +* [Documentation](https://playwright.dev) +* [API reference](https://playwright.dev/docs/api/class-playwright/) +* [Contribution guide](CONTRIBUTING.md) +* [Changelog](https://github.com/microsoft/playwright/releases) diff --git a/tests/node_modules/@playwright/test/cli.js b/tests/node_modules/@playwright/test/cli.js new file mode 100755 index 00000000..e42facb0 --- /dev/null +++ b/tests/node_modules/@playwright/test/cli.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { program } = require('playwright/lib/program'); +program.parse(process.argv); diff --git a/tests/node_modules/@playwright/test/index.d.ts b/tests/node_modules/@playwright/test/index.d.ts new file mode 100644 index 00000000..8d99c915 --- /dev/null +++ b/tests/node_modules/@playwright/test/index.d.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from 'playwright/test'; +export { default } from 'playwright/test'; diff --git a/tests/node_modules/@playwright/test/index.js b/tests/node_modules/@playwright/test/index.js new file mode 100644 index 00000000..8536f063 --- /dev/null +++ b/tests/node_modules/@playwright/test/index.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = require('playwright/test'); diff --git a/tests/node_modules/@playwright/test/index.mjs b/tests/node_modules/@playwright/test/index.mjs new file mode 100644 index 00000000..8d99c915 --- /dev/null +++ b/tests/node_modules/@playwright/test/index.mjs @@ -0,0 +1,18 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from 'playwright/test'; +export { default } from 'playwright/test'; diff --git a/tests/node_modules/@playwright/test/package.json b/tests/node_modules/@playwright/test/package.json new file mode 100644 index 00000000..bef46d89 --- /dev/null +++ b/tests/node_modules/@playwright/test/package.json @@ -0,0 +1,35 @@ +{ + "name": "@playwright/test", + "version": "1.57.0", + "description": "A high-level API to automate web browsers", + "repository": { + "type": "git", + "url": "git+https://github.com/microsoft/playwright.git" + }, + "homepage": "https://playwright.dev", + "engines": { + "node": ">=18" + }, + "author": { + "name": "Microsoft Corporation" + }, + "license": "Apache-2.0", + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./index.mjs", + "require": "./index.js", + "default": "./index.js" + }, + "./cli": "./cli.js", + "./package.json": "./package.json", + "./reporter": "./reporter.js" + }, + "bin": { + "playwright": "cli.js" + }, + "scripts": {}, + "dependencies": { + "playwright": "1.57.0" + } +} diff --git a/tests/node_modules/@playwright/test/reporter.d.ts b/tests/node_modules/@playwright/test/reporter.d.ts new file mode 100644 index 00000000..806d13fb --- /dev/null +++ b/tests/node_modules/@playwright/test/reporter.d.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from 'playwright/types/testReporter'; diff --git a/tests/node_modules/@playwright/test/reporter.js b/tests/node_modules/@playwright/test/reporter.js new file mode 100644 index 00000000..485e880a --- /dev/null +++ b/tests/node_modules/@playwright/test/reporter.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We only export types in reporter.d.ts. diff --git a/tests/node_modules/@playwright/test/reporter.mjs b/tests/node_modules/@playwright/test/reporter.mjs new file mode 100644 index 00000000..485e880a --- /dev/null +++ b/tests/node_modules/@playwright/test/reporter.mjs @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// We only export types in reporter.d.ts. diff --git a/tests/node_modules/playwright-core/LICENSE b/tests/node_modules/playwright-core/LICENSE new file mode 100644 index 00000000..df112373 --- /dev/null +++ b/tests/node_modules/playwright-core/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tests/node_modules/playwright-core/NOTICE b/tests/node_modules/playwright-core/NOTICE new file mode 100644 index 00000000..814ec169 --- /dev/null +++ b/tests/node_modules/playwright-core/NOTICE @@ -0,0 +1,5 @@ +Playwright +Copyright (c) Microsoft Corporation + +This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer), +available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE). diff --git a/tests/node_modules/playwright-core/README.md b/tests/node_modules/playwright-core/README.md new file mode 100644 index 00000000..422b3739 --- /dev/null +++ b/tests/node_modules/playwright-core/README.md @@ -0,0 +1,3 @@ +# playwright-core + +This package contains the no-browser flavor of [Playwright](http://github.com/microsoft/playwright). diff --git a/tests/node_modules/playwright-core/ThirdPartyNotices.txt b/tests/node_modules/playwright-core/ThirdPartyNotices.txt new file mode 100644 index 00000000..1ad3449f --- /dev/null +++ b/tests/node_modules/playwright-core/ThirdPartyNotices.txt @@ -0,0 +1,1161 @@ +microsoft/playwright-core + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION + +This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. + +- agent-base@7.1.4 (https://github.com/TooTallNate/proxy-agents) +- balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match) +- brace-expansion@1.1.12 (https://github.com/juliangruber/brace-expansion) +- buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32) +- codemirror@5.65.18 (https://github.com/codemirror/CodeMirror) +- colors@1.4.0 (https://github.com/Marak/colors.js) +- commander@13.1.0 (https://github.com/tj/commander.js) +- concat-map@0.0.1 (https://github.com/substack/node-concat-map) +- debug@4.3.4 (https://github.com/debug-js/debug) +- debug@4.4.0 (https://github.com/debug-js/debug) +- define-lazy-prop@2.0.0 (https://github.com/sindresorhus/define-lazy-prop) +- diff@7.0.0 (https://github.com/kpdecker/jsdiff) +- dotenv@16.4.5 (https://github.com/motdotla/dotenv) +- end-of-stream@1.4.4 (https://github.com/mafintosh/end-of-stream) +- get-stream@5.2.0 (https://github.com/sindresorhus/get-stream) +- graceful-fs@4.2.10 (https://github.com/isaacs/node-graceful-fs) +- https-proxy-agent@7.0.6 (https://github.com/TooTallNate/proxy-agents) +- ip-address@9.0.5 (https://github.com/beaugunderson/ip-address) +- is-docker@2.2.1 (https://github.com/sindresorhus/is-docker) +- is-wsl@2.2.0 (https://github.com/sindresorhus/is-wsl) +- jpeg-js@0.4.4 (https://github.com/eugeneware/jpeg-js) +- jsbn@1.1.0 (https://github.com/andyperlitch/jsbn) +- mime@3.0.0 (https://github.com/broofa/mime) +- minimatch@3.1.2 (https://github.com/isaacs/minimatch) +- ms@2.1.2 (https://github.com/zeit/ms) +- ms@2.1.3 (https://github.com/vercel/ms) +- once@1.4.0 (https://github.com/isaacs/once) +- open@8.4.0 (https://github.com/sindresorhus/open) +- pend@1.2.0 (https://github.com/andrewrk/node-pend) +- pngjs@6.0.0 (https://github.com/lukeapage/pngjs) +- progress@2.0.3 (https://github.com/visionmedia/node-progress) +- proxy-from-env@1.1.0 (https://github.com/Rob--W/proxy-from-env) +- pump@3.0.2 (https://github.com/mafintosh/pump) +- retry@0.12.0 (https://github.com/tim-kos/node-retry) +- signal-exit@3.0.7 (https://github.com/tapjs/signal-exit) +- smart-buffer@4.2.0 (https://github.com/JoshGlazebrook/smart-buffer) +- socks-proxy-agent@8.0.5 (https://github.com/TooTallNate/proxy-agents) +- socks@2.8.3 (https://github.com/JoshGlazebrook/socks) +- sprintf-js@1.1.3 (https://github.com/alexei/sprintf.js) +- wrappy@1.0.2 (https://github.com/npm/wrappy) +- ws@8.17.1 (https://github.com/websockets/ws) +- yaml@2.6.0 (https://github.com/eemeli/yaml) +- yauzl@3.2.0 (https://github.com/thejoshwolfe/yauzl) +- yazl@2.5.1 (https://github.com/thejoshwolfe/yazl) +- zod@3.25.76 (https://github.com/colinhacks/zod) + +%% agent-base@7.1.4 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2013 Nathan Rajlich + +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. +========================================= +END OF agent-base@7.1.4 AND INFORMATION + +%% balanced-match@1.0.2 NOTICES AND INFORMATION BEGIN HERE +========================================= +(MIT) + +Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + +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. +========================================= +END OF balanced-match@1.0.2 AND INFORMATION + +%% brace-expansion@1.1.12 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2013 Julian Gruber + +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. +========================================= +END OF brace-expansion@1.1.12 AND INFORMATION + +%% buffer-crc32@0.2.13 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License + +Copyright (c) 2013 Brian J. Brennan + +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. +========================================= +END OF buffer-crc32@0.2.13 AND INFORMATION + +%% codemirror@5.65.18 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (C) 2017 by Marijn Haverbeke and others + +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. +========================================= +END OF codemirror@5.65.18 AND INFORMATION + +%% colors@1.4.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Original Library + - Copyright (c) Marak Squires + +Additional Functionality + - Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. +========================================= +END OF colors@1.4.0 AND INFORMATION + +%% commander@13.1.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2011 TJ Holowaychuk + +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. +========================================= +END OF commander@13.1.0 AND INFORMATION + +%% concat-map@0.0.1 NOTICES AND INFORMATION BEGIN HERE +========================================= +This software is released under the MIT license: + +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. +========================================= +END OF concat-map@0.0.1 AND INFORMATION + +%% debug@4.3.4 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2014-2017 TJ Holowaychuk +Copyright (c) 2018-2021 Josh Junon + +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. +========================================= +END OF debug@4.3.4 AND INFORMATION + +%% debug@4.4.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2014-2017 TJ Holowaychuk +Copyright (c) 2018-2021 Josh Junon + +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. +========================================= +END OF debug@4.4.0 AND INFORMATION + +%% define-lazy-prop@2.0.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. +========================================= +END OF define-lazy-prop@2.0.0 AND INFORMATION + +%% diff@7.0.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +BSD 3-Clause License + +Copyright (c) 2009-2015, Kevin Decker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF diff@7.0.0 AND INFORMATION + +%% dotenv@16.4.5 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2015, Scott Motte +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF dotenv@16.4.5 AND INFORMATION + +%% end-of-stream@1.4.4 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2014 Mathias Buus + +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. +========================================= +END OF end-of-stream@1.4.4 AND INFORMATION + +%% get-stream@5.2.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +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. +========================================= +END OF get-stream@5.2.0 AND INFORMATION + +%% graceful-fs@4.2.10 NOTICES AND INFORMATION BEGIN HERE +========================================= +The ISC License + +Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF graceful-fs@4.2.10 AND INFORMATION + +%% https-proxy-agent@7.0.6 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2013 Nathan Rajlich + +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. +========================================= +END OF https-proxy-agent@7.0.6 AND INFORMATION + +%% ip-address@9.0.5 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (C) 2011 by Beau Gunderson + +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. +========================================= +END OF ip-address@9.0.5 AND INFORMATION + +%% is-docker@2.2.1 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +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. +========================================= +END OF is-docker@2.2.1 AND INFORMATION + +%% is-wsl@2.2.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. +========================================= +END OF is-wsl@2.2.0 AND INFORMATION + +%% jpeg-js@0.4.4 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2014, Eugene Ware +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Eugene Ware nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF jpeg-js@0.4.4 AND INFORMATION + +%% jsbn@1.1.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +Licensing +--------- + +This software is covered under the following copyright: + +/* + * Copyright (c) 2003-2005 Tom Wu + * All Rights Reserved. + * + * 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" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * In addition, the following condition applies: + * + * All redistributions must retain an intact copy of this copyright notice + * and disclaimer. + */ + +Address all questions regarding this license to: + + Tom Wu + tjw@cs.Stanford.EDU +========================================= +END OF jsbn@1.1.0 AND INFORMATION + +%% mime@3.0.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2010 Benjamin Thomas, Robert Kieffer + +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. +========================================= +END OF mime@3.0.0 AND INFORMATION + +%% minimatch@3.1.2 NOTICES AND INFORMATION BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF minimatch@3.1.2 AND INFORMATION + +%% ms@2.1.2 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2016 Zeit, Inc. + +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. +========================================= +END OF ms@2.1.2 AND INFORMATION + +%% ms@2.1.3 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2020 Vercel, Inc. + +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. +========================================= +END OF ms@2.1.3 AND INFORMATION + +%% once@1.4.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF once@1.4.0 AND INFORMATION + +%% open@8.4.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +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. +========================================= +END OF open@8.4.0 AND INFORMATION + +%% pend@1.2.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (Expat) + +Copyright (c) 2014 Andrew Kelley + +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. +========================================= +END OF pend@1.2.0 AND INFORMATION + +%% pngjs@6.0.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors +pngjs derived work Copyright (c) 2012 Kuba Niegowski + +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. +========================================= +END OF pngjs@6.0.0 AND INFORMATION + +%% progress@2.0.3 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2017 TJ Holowaychuk + +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. +========================================= +END OF progress@2.0.3 AND INFORMATION + +%% proxy-from-env@1.1.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License + +Copyright (C) 2016-2018 Rob Wu + +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. +========================================= +END OF proxy-from-env@1.1.0 AND INFORMATION + +%% pump@3.0.2 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2014 Mathias Buus + +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. +========================================= +END OF pump@3.0.2 AND INFORMATION + +%% retry@0.12.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2011: +Tim Koschützki (tim@debuggable.com) +Felix Geisendörfer (felix@debuggable.com) + + 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. +========================================= +END OF retry@0.12.0 AND INFORMATION + +%% signal-exit@3.0.7 NOTICES AND INFORMATION BEGIN HERE +========================================= +The ISC License + +Copyright (c) 2015, Contributors + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF signal-exit@3.0.7 AND INFORMATION + +%% smart-buffer@4.2.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2013-2017 Josh Glazebrook + +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. +========================================= +END OF smart-buffer@4.2.0 AND INFORMATION + +%% socks-proxy-agent@8.0.5 NOTICES AND INFORMATION BEGIN HERE +========================================= +(The MIT License) + +Copyright (c) 2013 Nathan Rajlich + +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. +========================================= +END OF socks-proxy-agent@8.0.5 AND INFORMATION + +%% socks@2.8.3 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2013 Josh Glazebrook + +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. +========================================= +END OF socks@2.8.3 AND INFORMATION + +%% sprintf-js@1.1.3 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2007-present, Alexandru Mărășteanu +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of this software nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +END OF sprintf-js@1.1.3 AND INFORMATION + +%% wrappy@1.0.2 NOTICES AND INFORMATION BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF wrappy@1.0.2 AND INFORMATION + +%% ws@8.17.1 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2011 Einar Otto Stangvik +Copyright (c) 2013 Arnout Kazemier and contributors +Copyright (c) 2016 Luigi Pinca and contributors + +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. +========================================= +END OF ws@8.17.1 AND INFORMATION + +%% yaml@2.6.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright Eemeli Aro + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. +========================================= +END OF yaml@2.6.0 AND INFORMATION + +%% yauzl@3.2.0 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2014 Josh Wolfe + +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. +========================================= +END OF yauzl@3.2.0 AND INFORMATION + +%% yazl@2.5.1 NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2014 Josh Wolfe + +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. +========================================= +END OF yazl@2.5.1 AND INFORMATION + +%% zod@3.25.76 NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2025 Colin McDonnell + +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. +========================================= +END OF zod@3.25.76 AND INFORMATION + +SUMMARY BEGIN HERE +========================================= +Total Packages: 45 +========================================= +END OF SUMMARY \ No newline at end of file diff --git a/tests/node_modules/playwright-core/bin/install_media_pack.ps1 b/tests/node_modules/playwright-core/bin/install_media_pack.ps1 new file mode 100644 index 00000000..61707542 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/install_media_pack.ps1 @@ -0,0 +1,5 @@ +$osInfo = Get-WmiObject -Class Win32_OperatingSystem +# check if running on Windows Server +if ($osInfo.ProductType -eq 3) { + Install-WindowsFeature Server-Media-Foundation +} diff --git a/tests/node_modules/playwright-core/bin/install_webkit_wsl.ps1 b/tests/node_modules/playwright-core/bin/install_webkit_wsl.ps1 new file mode 100644 index 00000000..ccaaf156 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/install_webkit_wsl.ps1 @@ -0,0 +1,33 @@ +$ErrorActionPreference = 'Stop' + +# This script sets up a WSL distribution that will be used to run WebKit. + +$Distribution = "playwright" +$Username = "pwuser" + +$distributions = (wsl --list --quiet) -split "\r?\n" +if ($distributions -contains $Distribution) { + Write-Host "WSL distribution '$Distribution' already exists. Skipping installation." +} else { + Write-Host "Installing new WSL distribution '$Distribution'..." + $VhdSize = "10GB" + wsl --install -d Ubuntu-24.04 --name $Distribution --no-launch --vhd-size $VhdSize + wsl -d $Distribution -u root adduser --gecos GECOS --disabled-password $Username +} + +$pwshDirname = (Resolve-Path -Path $PSScriptRoot).Path; +$playwrightCoreRoot = Resolve-Path (Join-Path $pwshDirname "..") + +$initScript = @" +if [ ! -f "/home/$Username/node/bin/node" ]; then + mkdir -p /home/$Username/node + curl -fsSL https://nodejs.org/dist/v22.17.0/node-v22.17.0-linux-x64.tar.xz -o /home/$Username/node/node-v22.17.0-linux-x64.tar.xz + tar -xJf /home/$Username/node/node-v22.17.0-linux-x64.tar.xz -C /home/$Username/node --strip-components=1 + sudo -u $Username echo 'export PATH=/home/$Username/node/bin:\`$PATH' >> /home/$Username/.profile +fi +/home/$Username/node/bin/node cli.js install-deps webkit +sudo -u $Username PLAYWRIGHT_SKIP_BROWSER_GC=1 /home/$Username/node/bin/node cli.js install webkit +"@ -replace "\r\n", "`n" + +wsl -d $Distribution --cd $playwrightCoreRoot -u root -- bash -c "$initScript" +Write-Host "Done!" \ No newline at end of file diff --git a/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh b/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh new file mode 100755 index 00000000..0451bda3 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -e +set -x + +if [[ $(arch) == "aarch64" ]]; then + echo "ERROR: not supported on Linux Arm64" + exit 1 +fi + +if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then + if [[ ! -f "/etc/os-release" ]]; then + echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)" + exit 1 + fi + + ID=$(bash -c 'source /etc/os-release && echo $ID') + if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then + echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported" + exit 1 + fi +fi + +# 1. make sure to remove old beta if any. +if dpkg --get-selections | grep -q "^google-chrome-beta[[:space:]]*install$" >/dev/null; then + apt-get remove -y google-chrome-beta +fi + +# 2. Update apt lists (needed to install curl and chrome dependencies) +apt-get update + +# 3. Install curl to download chrome +if ! command -v curl >/dev/null; then + apt-get install -y curl +fi + +# 4. download chrome beta from dl.google.com and install it. +cd /tmp +curl -O https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb +apt-get install -y ./google-chrome-beta_current_amd64.deb +rm -rf ./google-chrome-beta_current_amd64.deb +cd - +google-chrome-beta --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh b/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh new file mode 100755 index 00000000..617e3b5e --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e +set -x + +rm -rf "/Applications/Google Chrome Beta.app" +cd /tmp +curl --retry 3 -o ./googlechromebeta.dmg https://dl.google.com/chrome/mac/universal/beta/googlechromebeta.dmg +hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechromebeta.dmg ./googlechromebeta.dmg +cp -pR "/Volumes/googlechromebeta.dmg/Google Chrome Beta.app" /Applications +hdiutil detach /Volumes/googlechromebeta.dmg +rm -rf /tmp/googlechromebeta.dmg + +/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 b/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 new file mode 100644 index 00000000..3fbe5515 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 @@ -0,0 +1,24 @@ +$ErrorActionPreference = 'Stop' + +$url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromebetastandaloneenterprise64.msi' + +Write-Host "Downloading Google Chrome Beta" +$wc = New-Object net.webclient +$msiInstaller = "$env:temp\google-chrome-beta.msi" +$wc.Downloadfile($url, $msiInstaller) + +Write-Host "Installing Google Chrome Beta" +$arguments = "/i `"$msiInstaller`" /quiet" +Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + +$suffix = "\\Google\\Chrome Beta\\Application\\chrome.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + Write-Host "ERROR: Failed to install Google Chrome Beta." + Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help." + exit 1 +} diff --git a/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh b/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh new file mode 100755 index 00000000..78f1d413 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -e +set -x + +if [[ $(arch) == "aarch64" ]]; then + echo "ERROR: not supported on Linux Arm64" + exit 1 +fi + +if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then + if [[ ! -f "/etc/os-release" ]]; then + echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)" + exit 1 + fi + + ID=$(bash -c 'source /etc/os-release && echo $ID') + if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then + echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported" + exit 1 + fi +fi + +# 1. make sure to remove old stable if any. +if dpkg --get-selections | grep -q "^google-chrome[[:space:]]*install$" >/dev/null; then + apt-get remove -y google-chrome +fi + +# 2. Update apt lists (needed to install curl and chrome dependencies) +apt-get update + +# 3. Install curl to download chrome +if ! command -v curl >/dev/null; then + apt-get install -y curl +fi + +# 4. download chrome stable from dl.google.com and install it. +cd /tmp +curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb +apt-get install -y ./google-chrome-stable_current_amd64.deb +rm -rf ./google-chrome-stable_current_amd64.deb +cd - +google-chrome --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh b/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh new file mode 100755 index 00000000..6aa650a5 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e +set -x + +rm -rf "/Applications/Google Chrome.app" +cd /tmp +curl --retry 3 -o ./googlechrome.dmg https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg +hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechrome.dmg ./googlechrome.dmg +cp -pR "/Volumes/googlechrome.dmg/Google Chrome.app" /Applications +hdiutil detach /Volumes/googlechrome.dmg +rm -rf /tmp/googlechrome.dmg +/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 b/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 new file mode 100644 index 00000000..7ca2dbaf --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 @@ -0,0 +1,24 @@ +$ErrorActionPreference = 'Stop' +$url = 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise64.msi' + +$wc = New-Object net.webclient +$msiInstaller = "$env:temp\google-chrome.msi" +Write-Host "Downloading Google Chrome" +$wc.Downloadfile($url, $msiInstaller) + +Write-Host "Installing Google Chrome" +$arguments = "/i `"$msiInstaller`" /quiet" +Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + + +$suffix = "\\Google\\Chrome\\Application\\chrome.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + Write-Host "ERROR: Failed to install Google Chrome." + Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help." + exit 1 +} diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh b/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh new file mode 100755 index 00000000..a1531a95 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -e +set -x + +if [[ $(arch) == "aarch64" ]]; then + echo "ERROR: not supported on Linux Arm64" + exit 1 +fi + +if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then + if [[ ! -f "/etc/os-release" ]]; then + echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)" + exit 1 + fi + + ID=$(bash -c 'source /etc/os-release && echo $ID') + if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then + echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported" + exit 1 + fi +fi + +# 1. make sure to remove old beta if any. +if dpkg --get-selections | grep -q "^microsoft-edge-beta[[:space:]]*install$" >/dev/null; then + apt-get remove -y microsoft-edge-beta +fi + +# 2. Install curl to download Microsoft gpg key +if ! command -v curl >/dev/null; then + apt-get update + apt-get install -y curl +fi + +# GnuPG is not preinstalled in slim images +if ! command -v gpg >/dev/null; then + apt-get update + apt-get install -y gpg +fi + +# 3. Add the GPG key, the apt repo, update the apt cache, and install the package +curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg +install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/ +sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list' +rm /tmp/microsoft.gpg +apt-get update && apt-get install -y microsoft-edge-beta + +microsoft-edge-beta --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh b/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh new file mode 100755 index 00000000..72ec3e4e --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e +set -x + +cd /tmp +curl --retry 3 -o ./msedge_beta.pkg "$1" +# Note: there's no way to uninstall previously installed MSEdge. +# However, running PKG again seems to update installation. +sudo installer -pkg /tmp/msedge_beta.pkg -target / +rm -rf /tmp/msedge_beta.pkg +/Applications/Microsoft\ Edge\ Beta.app/Contents/MacOS/Microsoft\ Edge\ Beta --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 b/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 new file mode 100644 index 00000000..cce0d0bf --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 @@ -0,0 +1,23 @@ +$ErrorActionPreference = 'Stop' +$url = $args[0] + +Write-Host "Downloading Microsoft Edge Beta" +$wc = New-Object net.webclient +$msiInstaller = "$env:temp\microsoft-edge-beta.msi" +$wc.Downloadfile($url, $msiInstaller) + +Write-Host "Installing Microsoft Edge Beta" +$arguments = "/i `"$msiInstaller`" /quiet" +Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + +$suffix = "\\Microsoft\\Edge Beta\\Application\\msedge.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + Write-Host "ERROR: Failed to install Microsoft Edge Beta." + Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help." + exit 1 +} diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh b/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh new file mode 100755 index 00000000..7fde34e5 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -e +set -x + +if [[ $(arch) == "aarch64" ]]; then + echo "ERROR: not supported on Linux Arm64" + exit 1 +fi + +if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then + if [[ ! -f "/etc/os-release" ]]; then + echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)" + exit 1 + fi + + ID=$(bash -c 'source /etc/os-release && echo $ID') + if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then + echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported" + exit 1 + fi +fi + +# 1. make sure to remove old dev if any. +if dpkg --get-selections | grep -q "^microsoft-edge-dev[[:space:]]*install$" >/dev/null; then + apt-get remove -y microsoft-edge-dev +fi + +# 2. Install curl to download Microsoft gpg key +if ! command -v curl >/dev/null; then + apt-get update + apt-get install -y curl +fi + +# GnuPG is not preinstalled in slim images +if ! command -v gpg >/dev/null; then + apt-get update + apt-get install -y gpg +fi + +# 3. Add the GPG key, the apt repo, update the apt cache, and install the package +curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg +install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/ +sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list' +rm /tmp/microsoft.gpg +apt-get update && apt-get install -y microsoft-edge-dev + +microsoft-edge-dev --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh b/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh new file mode 100755 index 00000000..3376e869 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e +set -x + +cd /tmp +curl --retry 3 -o ./msedge_dev.pkg "$1" +# Note: there's no way to uninstall previously installed MSEdge. +# However, running PKG again seems to update installation. +sudo installer -pkg /tmp/msedge_dev.pkg -target / +rm -rf /tmp/msedge_dev.pkg +/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 b/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 new file mode 100644 index 00000000..22e6db84 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 @@ -0,0 +1,23 @@ +$ErrorActionPreference = 'Stop' +$url = $args[0] + +Write-Host "Downloading Microsoft Edge Dev" +$wc = New-Object net.webclient +$msiInstaller = "$env:temp\microsoft-edge-dev.msi" +$wc.Downloadfile($url, $msiInstaller) + +Write-Host "Installing Microsoft Edge Dev" +$arguments = "/i `"$msiInstaller`" /quiet" +Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + +$suffix = "\\Microsoft\\Edge Dev\\Application\\msedge.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + Write-Host "ERROR: Failed to install Microsoft Edge Dev." + Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help." + exit 1 +} diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh b/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh new file mode 100755 index 00000000..4acb1dbf --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -e +set -x + +if [[ $(arch) == "aarch64" ]]; then + echo "ERROR: not supported on Linux Arm64" + exit 1 +fi + +if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then + if [[ ! -f "/etc/os-release" ]]; then + echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)" + exit 1 + fi + + ID=$(bash -c 'source /etc/os-release && echo $ID') + if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then + echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported" + exit 1 + fi +fi + +# 1. make sure to remove old stable if any. +if dpkg --get-selections | grep -q "^microsoft-edge-stable[[:space:]]*install$" >/dev/null; then + apt-get remove -y microsoft-edge-stable +fi + +# 2. Install curl to download Microsoft gpg key +if ! command -v curl >/dev/null; then + apt-get update + apt-get install -y curl +fi + +# GnuPG is not preinstalled in slim images +if ! command -v gpg >/dev/null; then + apt-get update + apt-get install -y gpg +fi + +# 3. Add the GPG key, the apt repo, update the apt cache, and install the package +curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg +install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/ +sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-stable.list' +rm /tmp/microsoft.gpg +apt-get update && apt-get install -y microsoft-edge-stable + +microsoft-edge-stable --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh b/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh new file mode 100755 index 00000000..afcd2f53 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e +set -x + +cd /tmp +curl --retry 3 -o ./msedge_stable.pkg "$1" +# Note: there's no way to uninstall previously installed MSEdge. +# However, running PKG again seems to update installation. +sudo installer -pkg /tmp/msedge_stable.pkg -target / +rm -rf /tmp/msedge_stable.pkg +/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge --version diff --git a/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 b/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 new file mode 100644 index 00000000..31fdf513 --- /dev/null +++ b/tests/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 @@ -0,0 +1,24 @@ +$ErrorActionPreference = 'Stop' + +$url = $args[0] + +Write-Host "Downloading Microsoft Edge" +$wc = New-Object net.webclient +$msiInstaller = "$env:temp\microsoft-edge-stable.msi" +$wc.Downloadfile($url, $msiInstaller) + +Write-Host "Installing Microsoft Edge" +$arguments = "/i `"$msiInstaller`" /quiet" +Start-Process msiexec.exe -ArgumentList $arguments -Wait +Remove-Item $msiInstaller + +$suffix = "\\Microsoft\\Edge\\Application\\msedge.exe" +if (Test-Path "${env:ProgramFiles(x86)}$suffix") { + (Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo +} elseif (Test-Path "${env:ProgramFiles}$suffix") { + (Get-Item "${env:ProgramFiles}$suffix").VersionInfo +} else { + Write-Host "ERROR: Failed to install Microsoft Edge." + Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help." + exit 1 +} \ No newline at end of file diff --git a/tests/node_modules/playwright-core/browsers.json b/tests/node_modules/playwright-core/browsers.json new file mode 100644 index 00000000..a8260fa1 --- /dev/null +++ b/tests/node_modules/playwright-core/browsers.json @@ -0,0 +1,80 @@ +{ + "comment": "Do not edit this file, use utils/roll_browser.js", + "browsers": [ + { + "name": "chromium", + "revision": "1200", + "installByDefault": true, + "browserVersion": "143.0.7499.4" + }, + { + "name": "chromium-headless-shell", + "revision": "1200", + "installByDefault": true, + "browserVersion": "143.0.7499.4" + }, + { + "name": "chromium-tip-of-tree", + "revision": "1380", + "installByDefault": false, + "browserVersion": "143.0.7488.0" + }, + { + "name": "chromium-tip-of-tree-headless-shell", + "revision": "1380", + "installByDefault": false, + "browserVersion": "143.0.7488.0" + }, + { + "name": "firefox", + "revision": "1497", + "installByDefault": true, + "browserVersion": "144.0.2" + }, + { + "name": "firefox-beta", + "revision": "1493", + "installByDefault": false, + "browserVersion": "145.0b10" + }, + { + "name": "webkit", + "revision": "2227", + "installByDefault": true, + "revisionOverrides": { + "debian11-x64": "2105", + "debian11-arm64": "2105", + "mac10.14": "1446", + "mac10.15": "1616", + "mac11": "1816", + "mac11-arm64": "1816", + "mac12": "2009", + "mac12-arm64": "2009", + "mac13": "2140", + "mac13-arm64": "2140", + "ubuntu20.04-x64": "2092", + "ubuntu20.04-arm64": "2092" + }, + "browserVersion": "26.0" + }, + { + "name": "ffmpeg", + "revision": "1011", + "installByDefault": true, + "revisionOverrides": { + "mac12": "1010", + "mac12-arm64": "1010" + } + }, + { + "name": "winldd", + "revision": "1007", + "installByDefault": false + }, + { + "name": "android", + "revision": "1001", + "installByDefault": false + } + ] +} diff --git a/tests/node_modules/playwright-core/cli.js b/tests/node_modules/playwright-core/cli.js new file mode 100755 index 00000000..fb309ead --- /dev/null +++ b/tests/node_modules/playwright-core/cli.js @@ -0,0 +1,18 @@ +#!/usr/bin/env node +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const { program } = require('./lib/cli/programWithTestStub'); +program.parse(process.argv); diff --git a/tests/node_modules/playwright-core/index.d.ts b/tests/node_modules/playwright-core/index.d.ts new file mode 100644 index 00000000..97c14936 --- /dev/null +++ b/tests/node_modules/playwright-core/index.d.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './types/types'; diff --git a/tests/node_modules/playwright-core/index.js b/tests/node_modules/playwright-core/index.js new file mode 100644 index 00000000..d4991d0e --- /dev/null +++ b/tests/node_modules/playwright-core/index.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const minimumMajorNodeVersion = 18; +const currentNodeVersion = process.versions.node; +const semver = currentNodeVersion.split('.'); +const [major] = [+semver[0]]; + +if (major < minimumMajorNodeVersion) { + console.error( + 'You are running Node.js ' + + currentNodeVersion + + '.\n' + + `Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` + + 'Please update your version of Node.js.' + ); + process.exit(1); +} + +module.exports = require('./lib/inprocess'); diff --git a/tests/node_modules/playwright-core/index.mjs b/tests/node_modules/playwright-core/index.mjs new file mode 100644 index 00000000..3b3c75b0 --- /dev/null +++ b/tests/node_modules/playwright-core/index.mjs @@ -0,0 +1,28 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import playwright from './index.js'; + +export const chromium = playwright.chromium; +export const firefox = playwright.firefox; +export const webkit = playwright.webkit; +export const selectors = playwright.selectors; +export const devices = playwright.devices; +export const errors = playwright.errors; +export const request = playwright.request; +export const _electron = playwright._electron; +export const _android = playwright._android; +export default playwright; diff --git a/tests/node_modules/playwright-core/lib/androidServerImpl.js b/tests/node_modules/playwright-core/lib/androidServerImpl.js new file mode 100644 index 00000000..568548b7 --- /dev/null +++ b/tests/node_modules/playwright-core/lib/androidServerImpl.js @@ -0,0 +1,65 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var androidServerImpl_exports = {}; +__export(androidServerImpl_exports, { + AndroidServerLauncherImpl: () => AndroidServerLauncherImpl +}); +module.exports = __toCommonJS(androidServerImpl_exports); +var import_playwrightServer = require("./remote/playwrightServer"); +var import_playwright = require("./server/playwright"); +var import_crypto = require("./server/utils/crypto"); +var import_utilsBundle = require("./utilsBundle"); +var import_progress = require("./server/progress"); +class AndroidServerLauncherImpl { + async launchServer(options = {}) { + const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true }); + const controller = new import_progress.ProgressController(); + let devices = await controller.run((progress) => playwright.android.devices(progress, { + host: options.adbHost, + port: options.adbPort, + omitDriverInstall: options.omitDriverInstall + })); + if (devices.length === 0) + throw new Error("No devices found"); + if (options.deviceSerialNumber) { + devices = devices.filter((d) => d.serial === options.deviceSerialNumber); + if (devices.length === 0) + throw new Error(`No device with serial number '${options.deviceSerialNumber}' was found`); + } + if (devices.length > 1) + throw new Error(`More than one device found. Please specify deviceSerialNumber`); + const device = devices[0]; + const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`; + const server = new import_playwrightServer.PlaywrightServer({ mode: "launchServer", path, maxConnections: 1, preLaunchedAndroidDevice: device }); + const wsEndpoint = await server.listen(options.port, options.host); + const browserServer = new import_utilsBundle.ws.EventEmitter(); + browserServer.wsEndpoint = () => wsEndpoint; + browserServer.close = () => device.close(); + browserServer.kill = () => device.close(); + device.on("close", () => { + server.close(); + browserServer.emit("close"); + }); + return browserServer; + } +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + AndroidServerLauncherImpl +}); diff --git a/tests/node_modules/playwright-core/lib/browserServerImpl.js b/tests/node_modules/playwright-core/lib/browserServerImpl.js new file mode 100644 index 00000000..ac2b25d8 --- /dev/null +++ b/tests/node_modules/playwright-core/lib/browserServerImpl.js @@ -0,0 +1,120 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var browserServerImpl_exports = {}; +__export(browserServerImpl_exports, { + BrowserServerLauncherImpl: () => BrowserServerLauncherImpl +}); +module.exports = __toCommonJS(browserServerImpl_exports); +var import_playwrightServer = require("./remote/playwrightServer"); +var import_helper = require("./server/helper"); +var import_playwright = require("./server/playwright"); +var import_crypto = require("./server/utils/crypto"); +var import_debug = require("./server/utils/debug"); +var import_stackTrace = require("./utils/isomorphic/stackTrace"); +var import_time = require("./utils/isomorphic/time"); +var import_utilsBundle = require("./utilsBundle"); +var validatorPrimitives = __toESM(require("./protocol/validatorPrimitives")); +var import_progress = require("./server/progress"); +class BrowserServerLauncherImpl { + constructor(browserName) { + this._browserName = browserName; + } + async launchServer(options = {}) { + const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true }); + const metadata = { id: "", startTime: 0, endTime: 0, type: "Internal", method: "", params: {}, log: [], internal: true }; + const validatorContext = { + tChannelImpl: (names, arg, path2) => { + throw new validatorPrimitives.ValidationError(`${path2}: channels are not expected in launchServer`); + }, + binary: "buffer", + isUnderTest: import_debug.isUnderTest + }; + let launchOptions = { + ...options, + ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0, + ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs), + env: options.env ? envObjectToArray(options.env) : void 0, + timeout: options.timeout ?? import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT + }; + let browser; + try { + const controller = new import_progress.ProgressController(metadata); + browser = await controller.run(async (progress) => { + if (options._userDataDir !== void 0) { + const validator = validatorPrimitives.scheme["BrowserTypeLaunchPersistentContextParams"]; + launchOptions = validator({ ...launchOptions, userDataDir: options._userDataDir }, "", validatorContext); + const context = await playwright[this._browserName].launchPersistentContext(progress, options._userDataDir, launchOptions); + return context._browser; + } else { + const validator = validatorPrimitives.scheme["BrowserTypeLaunchParams"]; + launchOptions = validator(launchOptions, "", validatorContext); + return await playwright[this._browserName].launch(progress, launchOptions, toProtocolLogger(options.logger)); + } + }); + } catch (e) { + const log = import_helper.helper.formatBrowserLogs(metadata.log); + (0, import_stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`); + throw e; + } + const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`; + const server = new import_playwrightServer.PlaywrightServer({ mode: options._sharedBrowser ? "launchServerShared" : "launchServer", path, maxConnections: Infinity, preLaunchedBrowser: browser }); + const wsEndpoint = await server.listen(options.port, options.host); + const browserServer = new import_utilsBundle.ws.EventEmitter(); + browserServer.process = () => browser.options.browserProcess.process; + browserServer.wsEndpoint = () => wsEndpoint; + browserServer.close = () => browser.options.browserProcess.close(); + browserServer[Symbol.asyncDispose] = browserServer.close; + browserServer.kill = () => browser.options.browserProcess.kill(); + browserServer._disconnectForTest = () => server.close(); + browserServer._userDataDirForTest = browser._userDataDirForTest; + browser.options.browserProcess.onclose = (exitCode, signal) => { + server.close(); + browserServer.emit("close", exitCode, signal); + }; + return browserServer; + } +} +function toProtocolLogger(logger) { + return logger ? (direction, message) => { + if (logger.isEnabled("protocol", "verbose")) + logger.log("protocol", "verbose", (direction === "send" ? "SEND \u25BA " : "\u25C0 RECV ") + JSON.stringify(message), [], {}); + } : void 0; +} +function envObjectToArray(env) { + const result = []; + for (const name in env) { + if (!Object.is(env[name], void 0)) + result.push({ name, value: String(env[name]) }); + } + return result; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + BrowserServerLauncherImpl +}); diff --git a/tests/node_modules/playwright-core/lib/cli/driver.js b/tests/node_modules/playwright-core/lib/cli/driver.js new file mode 100644 index 00000000..a389e152 --- /dev/null +++ b/tests/node_modules/playwright-core/lib/cli/driver.js @@ -0,0 +1,97 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var driver_exports = {}; +__export(driver_exports, { + launchBrowserServer: () => launchBrowserServer, + printApiJson: () => printApiJson, + runDriver: () => runDriver, + runServer: () => runServer +}); +module.exports = __toCommonJS(driver_exports); +var import_fs = __toESM(require("fs")); +var playwright = __toESM(require("../..")); +var import_pipeTransport = require("../server/utils/pipeTransport"); +var import_playwrightServer = require("../remote/playwrightServer"); +var import_server = require("../server"); +var import_processLauncher = require("../server/utils/processLauncher"); +function printApiJson() { + console.log(JSON.stringify(require("../../api.json"))); +} +function runDriver() { + const dispatcherConnection = new import_server.DispatcherConnection(); + new import_server.RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => { + const playwright2 = (0, import_server.createPlaywright)({ sdkLanguage }); + return new import_server.PlaywrightDispatcher(rootScope, playwright2); + }); + const transport = new import_pipeTransport.PipeTransport(process.stdout, process.stdin); + transport.onmessage = (message) => dispatcherConnection.dispatch(JSON.parse(message)); + const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === "javascript"; + const replacer = !isJavaScriptLanguageBinding && String.prototype.toWellFormed ? (key, value) => { + if (typeof value === "string") + return value.toWellFormed(); + return value; + } : void 0; + dispatcherConnection.onmessage = (message) => transport.send(JSON.stringify(message, replacer)); + transport.onclose = () => { + dispatcherConnection.onmessage = () => { + }; + (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0); + }; + process.on("SIGINT", () => { + }); +} +async function runServer(options) { + const { + port, + host, + path = "/", + maxConnections = Infinity, + extension + } = options; + const server = new import_playwrightServer.PlaywrightServer({ mode: extension ? "extension" : "default", path, maxConnections }); + const wsEndpoint = await server.listen(port, host); + process.on("exit", () => server.close().catch(console.error)); + console.log("Listening on " + wsEndpoint); + process.stdin.on("close", () => (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0)); +} +async function launchBrowserServer(browserName, configFile) { + let options = {}; + if (configFile) + options = JSON.parse(import_fs.default.readFileSync(configFile).toString()); + const browserType = playwright[browserName]; + const server = await browserType.launchServer(options); + console.log(server.wsEndpoint()); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + launchBrowserServer, + printApiJson, + runDriver, + runServer +}); diff --git a/tests/node_modules/playwright-core/lib/cli/program.js b/tests/node_modules/playwright-core/lib/cli/program.js new file mode 100644 index 00000000..86238de6 --- /dev/null +++ b/tests/node_modules/playwright-core/lib/cli/program.js @@ -0,0 +1,590 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var program_exports = {}; +__export(program_exports, { + program: () => import_utilsBundle2.program +}); +module.exports = __toCommonJS(program_exports); +var import_fs = __toESM(require("fs")); +var import_os = __toESM(require("os")); +var import_path = __toESM(require("path")); +var playwright = __toESM(require("../..")); +var import_driver = require("./driver"); +var import_server = require("../server"); +var import_utils = require("../utils"); +var import_traceViewer = require("../server/trace/viewer/traceViewer"); +var import_utils2 = require("../utils"); +var import_ascii = require("../server/utils/ascii"); +var import_utilsBundle = require("../utilsBundle"); +var import_utilsBundle2 = require("../utilsBundle"); +const packageJSON = require("../../package.json"); +import_utilsBundle.program.version("Version " + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME)); +import_utilsBundle.program.command("mark-docker-image [dockerImageNameTemplate]", { hidden: true }).description("mark docker image").allowUnknownOption(true).action(function(dockerImageNameTemplate) { + (0, import_utils2.assert)(dockerImageNameTemplate, "dockerImageNameTemplate is required"); + (0, import_server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit); +}); +commandWithOpenOptions("open [url]", "open page in browser specified via -b, --browser", []).action(function(url, options) { + open(options, url).catch(logErrorAndExit); +}).addHelpText("afterAll", ` +Examples: + + $ open + $ open -b webkit https://example.com`); +commandWithOpenOptions( + "codegen [url]", + "open page and generate code for user actions", + [ + ["-o, --output ", "saves the generated script to a file"], + ["--target ", `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()], + ["--test-id-attribute ", "use the specified attribute to generate data test ID selectors"] + ] +).action(async function(url, options) { + await codegen(options, url); +}).addHelpText("afterAll", ` +Examples: + + $ codegen + $ codegen --target=python + $ codegen -b webkit https://example.com`); +function printInstalledBrowsers(browsers2) { + const browserPaths = /* @__PURE__ */ new Set(); + for (const browser of browsers2) + browserPaths.add(browser.browserPath); + console.log(` Browsers:`); + for (const browserPath of [...browserPaths].sort()) + console.log(` ${browserPath}`); + console.log(` References:`); + const references = /* @__PURE__ */ new Set(); + for (const browser of browsers2) + references.add(browser.referenceDir); + for (const reference of [...references].sort()) + console.log(` ${reference}`); +} +function printGroupedByPlaywrightVersion(browsers2) { + const dirToVersion = /* @__PURE__ */ new Map(); + for (const browser of browsers2) { + if (dirToVersion.has(browser.referenceDir)) + continue; + const packageJSON2 = require(import_path.default.join(browser.referenceDir, "package.json")); + const version = packageJSON2.version; + dirToVersion.set(browser.referenceDir, version); + } + const groupedByPlaywrightMinorVersion = /* @__PURE__ */ new Map(); + for (const browser of browsers2) { + const version = dirToVersion.get(browser.referenceDir); + let entries = groupedByPlaywrightMinorVersion.get(version); + if (!entries) { + entries = []; + groupedByPlaywrightMinorVersion.set(version, entries); + } + entries.push(browser); + } + const sortedVersions = [...groupedByPlaywrightMinorVersion.keys()].sort((a, b) => { + const aComponents = a.split("."); + const bComponents = b.split("."); + const aMajor = parseInt(aComponents[0], 10); + const bMajor = parseInt(bComponents[0], 10); + if (aMajor !== bMajor) + return aMajor - bMajor; + const aMinor = parseInt(aComponents[1], 10); + const bMinor = parseInt(bComponents[1], 10); + if (aMinor !== bMinor) + return aMinor - bMinor; + return aComponents.slice(2).join(".").localeCompare(bComponents.slice(2).join(".")); + }); + for (const version of sortedVersions) { + console.log(` +Playwright version: ${version}`); + printInstalledBrowsers(groupedByPlaywrightMinorVersion.get(version)); + } +} +import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--list", "prints list of browsers from all playwright installations").option("--force", "force reinstall of stable browser channels").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) { + if ((0, import_utils.isLikelyNpxGlobal)()) { + console.error((0, import_ascii.wrapInASCIIBox)([ + `WARNING: It looks like you are running 'npx playwright install' without first`, + `installing your project's dependencies.`, + ``, + `To avoid unexpected behavior, please install your dependencies first, and`, + `then run Playwright's install command:`, + ``, + ` npm install`, + ` npx playwright install`, + ``, + `If your project does not yet depend on Playwright, first install the`, + `applicable npm package (most commonly @playwright/test), and`, + `then run Playwright's install command to download the browsers:`, + ``, + ` npm install @playwright/test`, + ` npx playwright install`, + `` + ].join("\n"), 1)); + } + try { + if (options.shell === false && options.onlyShell) + throw new Error(`Only one of --no-shell and --only-shell can be specified`); + const shell = options.shell === false ? "no" : options.onlyShell ? "only" : void 0; + const executables = import_server.registry.resolveBrowsers(args, { shell }); + if (options.withDeps) + await import_server.registry.installDeps(executables, !!options.dryRun); + if (options.dryRun && options.list) + throw new Error(`Only one of --dry-run and --list can be specified`); + if (options.dryRun) { + for (const executable of executables) { + const version = executable.browserVersion ? `version ` + executable.browserVersion : ""; + console.log(`browser: ${executable.name}${version ? " " + version : ""}`); + console.log(` Install location: ${executable.directory ?? ""}`); + if (executable.downloadURLs?.length) { + const [url, ...fallbacks] = executable.downloadURLs; + console.log(` Download url: ${url}`); + for (let i = 0; i < fallbacks.length; ++i) + console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`); + } + console.log(``); + } + } else if (options.list) { + const browsers2 = await import_server.registry.listInstalledBrowsers(); + printGroupedByPlaywrightVersion(browsers2); + } else { + const force = args.length === 0 ? false : !!options.force; + await import_server.registry.install(executables, { force }); + await import_server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || "javascript").catch((e) => { + e.name = "Playwright Host validation warning"; + console.error(e); + }); + } + } catch (e) { + console.log(`Failed to install browsers +${e}`); + (0, import_utils.gracefullyProcessExitDoNotHang)(1); + } +}).addHelpText("afterAll", ` + +Examples: + - $ install + Install default browsers. + + - $ install chrome firefox + Install custom browsers, supports ${import_server.registry.suggestedBrowsersToInstall()}.`); +import_utilsBundle.program.command("uninstall").description("Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.").option("--all", "Removes all browsers used by any Playwright installation from the system.").action(async (options) => { + delete process.env.PLAYWRIGHT_SKIP_BROWSER_GC; + await import_server.registry.uninstall(!!options.all).then(({ numberOfBrowsersLeft }) => { + if (!options.all && numberOfBrowsersLeft > 0) { + console.log("Successfully uninstalled Playwright browsers for the current Playwright installation."); + console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations. +To uninstall Playwright browsers for all installations, re-run with --all flag.`); + } + }).catch(logErrorAndExit); +}); +import_utilsBundle.program.command("install-deps [browser...]").description("install dependencies necessary to run browsers (will ask for sudo permissions)").option("--dry-run", "Do not execute installation commands, only print them").action(async function(args, options) { + try { + await import_server.registry.installDeps(import_server.registry.resolveBrowsers(args, {}), !!options.dryRun); + } catch (e) { + console.log(`Failed to install browser dependencies +${e}`); + (0, import_utils.gracefullyProcessExitDoNotHang)(1); + } +}).addHelpText("afterAll", ` +Examples: + - $ install-deps + Install dependencies for default browsers. + + - $ install-deps chrome firefox + Install dependencies for specific browsers, supports ${import_server.registry.suggestedBrowsersToInstall()}.`); +const browsers = [ + { alias: "cr", name: "Chromium", type: "chromium" }, + { alias: "ff", name: "Firefox", type: "firefox" }, + { alias: "wk", name: "WebKit", type: "webkit" } +]; +for (const { alias, name, type } of browsers) { + commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(function(url, options) { + open({ ...options, browser: type }, url).catch(logErrorAndExit); + }).addHelpText("afterAll", ` +Examples: + + $ ${alias} https://example.com`); +} +commandWithOpenOptions( + "screenshot ", + "capture a page screenshot", + [ + ["--wait-for-selector ", "wait for selector before taking a screenshot"], + ["--wait-for-timeout ", "wait for timeout in milliseconds before taking a screenshot"], + ["--full-page", "whether to take a full page screenshot (entire scrollable area)"] + ] +).action(function(url, filename, command) { + screenshot(command, command, url, filename).catch(logErrorAndExit); +}).addHelpText("afterAll", ` +Examples: + + $ screenshot -b webkit https://example.com example.png`); +commandWithOpenOptions( + "pdf ", + "save page as pdf", + [ + ["--paper-format ", "paper format: Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6"], + ["--wait-for-selector ", "wait for given selector before saving as pdf"], + ["--wait-for-timeout ", "wait for given timeout in milliseconds before saving as pdf"] + ] +).action(function(url, filename, options) { + pdf(options, options, url, filename).catch(logErrorAndExit); +}).addHelpText("afterAll", ` +Examples: + + $ pdf https://example.com example.pdf`); +import_utilsBundle.program.command("run-driver", { hidden: true }).action(function(options) { + (0, import_driver.runDriver)(); +}); +import_utilsBundle.program.command("run-server", { hidden: true }).option("--port ", "Server port").option("--host ", "Server host").option("--path ", "Endpoint Path", "/").option("--max-clients ", "Maximum clients").option("--mode ", 'Server mode, either "default" or "extension"').action(function(options) { + (0, import_driver.runServer)({ + port: options.port ? +options.port : void 0, + host: options.host, + path: options.path, + maxConnections: options.maxClients ? +options.maxClients : Infinity, + extension: options.mode === "extension" || !!process.env.PW_EXTENSION_MODE + }).catch(logErrorAndExit); +}); +import_utilsBundle.program.command("print-api-json", { hidden: true }).action(function(options) { + (0, import_driver.printApiJson)(); +}); +import_utilsBundle.program.command("launch-server", { hidden: true }).requiredOption("--browser ", 'Browser name, one of "chromium", "firefox" or "webkit"').option("--config ", "JSON file with launchServer options").action(function(options) { + (0, import_driver.launchBrowserServer)(options.browser, options.config); +}); +import_utilsBundle.program.command("show-trace [trace]").option("-b, --browser ", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("-h, --host ", "Host to serve trace on; specifying this option opens trace in a browser tab").option("-p, --port ", "Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab").option("--stdin", "Accept trace URLs over stdin to update the viewer").description("show trace viewer").action(function(trace, options) { + if (options.browser === "cr") + options.browser = "chromium"; + if (options.browser === "ff") + options.browser = "firefox"; + if (options.browser === "wk") + options.browser = "webkit"; + const openOptions = { + host: options.host, + port: +options.port, + isServer: !!options.stdin + }; + if (options.port !== void 0 || options.host !== void 0) + (0, import_traceViewer.runTraceInBrowser)(trace, openOptions).catch(logErrorAndExit); + else + (0, import_traceViewer.runTraceViewerApp)(trace, options.browser, openOptions, true).catch(logErrorAndExit); +}).addHelpText("afterAll", ` +Examples: + + $ show-trace + $ show-trace https://example.com/trace.zip`); +async function launchContext(options, extraOptions) { + validateOptions(options); + const browserType = lookupBrowserType(options); + const launchOptions = extraOptions; + if (options.channel) + launchOptions.channel = options.channel; + launchOptions.handleSIGINT = false; + const contextOptions = ( + // Copy the device descriptor since we have to compare and modify the options. + options.device ? { ...playwright.devices[options.device] } : {} + ); + if (!extraOptions.headless) + contextOptions.deviceScaleFactor = import_os.default.platform() === "darwin" ? 2 : 1; + if (browserType.name() === "webkit" && process.platform === "linux") { + delete contextOptions.hasTouch; + delete contextOptions.isMobile; + } + if (contextOptions.isMobile && browserType.name() === "firefox") + contextOptions.isMobile = void 0; + if (options.blockServiceWorkers) + contextOptions.serviceWorkers = "block"; + if (options.proxyServer) { + launchOptions.proxy = { + server: options.proxyServer + }; + if (options.proxyBypass) + launchOptions.proxy.bypass = options.proxyBypass; + } + if (options.viewportSize) { + try { + const [width, height] = options.viewportSize.split(",").map((n) => +n); + if (isNaN(width) || isNaN(height)) + throw new Error("bad values"); + contextOptions.viewport = { width, height }; + } catch (e) { + throw new Error('Invalid viewport size format: use "width,height", for example --viewport-size="800,600"'); + } + } + if (options.geolocation) { + try { + const [latitude, longitude] = options.geolocation.split(",").map((n) => parseFloat(n.trim())); + contextOptions.geolocation = { + latitude, + longitude + }; + } catch (e) { + throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"'); + } + contextOptions.permissions = ["geolocation"]; + } + if (options.userAgent) + contextOptions.userAgent = options.userAgent; + if (options.lang) + contextOptions.locale = options.lang; + if (options.colorScheme) + contextOptions.colorScheme = options.colorScheme; + if (options.timezone) + contextOptions.timezoneId = options.timezone; + if (options.loadStorage) + contextOptions.storageState = options.loadStorage; + if (options.ignoreHttpsErrors) + contextOptions.ignoreHTTPSErrors = true; + if (options.saveHar) { + contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" }; + if (options.saveHarGlob) + contextOptions.recordHar.urlFilter = options.saveHarGlob; + contextOptions.serviceWorkers = "block"; + } + let browser; + let context; + if (options.userDataDir) { + context = await browserType.launchPersistentContext(options.userDataDir, { ...launchOptions, ...contextOptions }); + browser = context.browser(); + } else { + browser = await browserType.launch(launchOptions); + context = await browser.newContext(contextOptions); + } + let closingBrowser = false; + async function closeBrowser() { + if (closingBrowser) + return; + closingBrowser = true; + if (options.saveStorage) + await context.storageState({ path: options.saveStorage }).catch((e) => null); + if (options.saveHar) + await context.close(); + await browser.close(); + } + context.on("page", (page) => { + page.on("dialog", () => { + }); + page.on("close", () => { + const hasPage = browser.contexts().some((context2) => context2.pages().length > 0); + if (hasPage) + return; + closeBrowser().catch(() => { + }); + }); + }); + process.on("SIGINT", async () => { + await closeBrowser(); + (0, import_utils.gracefullyProcessExitDoNotHang)(130); + }); + const timeout = options.timeout ? parseInt(options.timeout, 10) : 0; + context.setDefaultTimeout(timeout); + context.setDefaultNavigationTimeout(timeout); + delete launchOptions.headless; + delete launchOptions.executablePath; + delete launchOptions.handleSIGINT; + delete contextOptions.deviceScaleFactor; + return { browser, browserName: browserType.name(), context, contextOptions, launchOptions, closeBrowser }; +} +async function openPage(context, url) { + let page = context.pages()[0]; + if (!page) + page = await context.newPage(); + if (url) { + if (import_fs.default.existsSync(url)) + url = "file://" + import_path.default.resolve(url); + else if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:") && !url.startsWith("data:")) + url = "http://" + url; + await page.goto(url); + } + return page; +} +async function open(options, url) { + const { context } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH }); + await openPage(context, url); +} +async function codegen(options, url) { + const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options; + const tracesDir = import_path.default.join(import_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`); + const { context, browser, launchOptions, contextOptions, closeBrowser } = await launchContext(options, { + headless: !!process.env.PWTEST_CLI_HEADLESS, + executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH, + tracesDir + }); + const donePromise = new import_utils.ManualPromise(); + maybeSetupTestHooks(browser, closeBrowser, donePromise); + import_utilsBundle.dotenv.config({ path: "playwright.env" }); + await context._enableRecorder({ + language, + launchOptions, + contextOptions, + device: options.device, + saveStorage: options.saveStorage, + mode: "recording", + testIdAttributeName, + outputFile: outputFile ? import_path.default.resolve(outputFile) : void 0, + handleSIGINT: false + }); + await openPage(context, url); + donePromise.resolve(); +} +async function maybeSetupTestHooks(browser, closeBrowser, donePromise) { + if (!process.env.PWTEST_CLI_IS_UNDER_TEST) + return; + const logs = []; + require("playwright-core/lib/utilsBundle").debug.log = (...args) => { + const line = require("util").format(...args) + "\n"; + logs.push(line); + process.stderr.write(line); + }; + browser.on("disconnected", () => { + const hasCrashLine = logs.some((line) => line.includes("process did exit:") && !line.includes("process did exit: exitCode=0, signal=null")); + if (hasCrashLine) { + process.stderr.write("Detected browser crash.\n"); + (0, import_utils.gracefullyProcessExitDoNotHang)(1); + } + }); + const close = async () => { + await donePromise; + await closeBrowser(); + }; + if (process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT) { + setTimeout(close, +process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT); + return; + } + let stdin = ""; + process.stdin.on("data", (data) => { + stdin += data.toString(); + if (stdin.startsWith("exit")) { + process.stdin.destroy(); + close(); + } + }); +} +async function waitForPage(page, captureOptions) { + if (captureOptions.waitForSelector) { + console.log(`Waiting for selector ${captureOptions.waitForSelector}...`); + await page.waitForSelector(captureOptions.waitForSelector); + } + if (captureOptions.waitForTimeout) { + console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`); + await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10)); + } +} +async function screenshot(options, captureOptions, url, path2) { + const { context } = await launchContext(options, { headless: true }); + console.log("Navigating to " + url); + const page = await openPage(context, url); + await waitForPage(page, captureOptions); + console.log("Capturing screenshot into " + path2); + await page.screenshot({ path: path2, fullPage: !!captureOptions.fullPage }); + await page.close(); +} +async function pdf(options, captureOptions, url, path2) { + if (options.browser !== "chromium") + throw new Error("PDF creation is only working with Chromium"); + const { context } = await launchContext({ ...options, browser: "chromium" }, { headless: true }); + console.log("Navigating to " + url); + const page = await openPage(context, url); + await waitForPage(page, captureOptions); + console.log("Saving as pdf into " + path2); + await page.pdf({ path: path2, format: captureOptions.paperFormat }); + await page.close(); +} +function lookupBrowserType(options) { + let name = options.browser; + if (options.device) { + const device = playwright.devices[options.device]; + name = device.defaultBrowserType; + } + let browserType; + switch (name) { + case "chromium": + browserType = playwright.chromium; + break; + case "webkit": + browserType = playwright.webkit; + break; + case "firefox": + browserType = playwright.firefox; + break; + case "cr": + browserType = playwright.chromium; + break; + case "wk": + browserType = playwright.webkit; + break; + case "ff": + browserType = playwright.firefox; + break; + } + if (browserType) + return browserType; + import_utilsBundle.program.help(); +} +function validateOptions(options) { + if (options.device && !(options.device in playwright.devices)) { + const lines = [`Device descriptor not found: '${options.device}', available devices are:`]; + for (const name in playwright.devices) + lines.push(` "${name}"`); + throw new Error(lines.join("\n")); + } + if (options.colorScheme && !["light", "dark"].includes(options.colorScheme)) + throw new Error('Invalid color scheme, should be one of "light", "dark"'); +} +function logErrorAndExit(e) { + if (process.env.PWDEBUGIMPL) + console.error(e); + else + console.error(e.name + ": " + e.message); + (0, import_utils.gracefullyProcessExitDoNotHang)(1); +} +function codegenId() { + return process.env.PW_LANG_NAME || "playwright-test"; +} +function commandWithOpenOptions(command, description, options) { + let result = import_utilsBundle.program.command(command).description(description); + for (const option of options) + result = result.option(option[0], ...option.slice(1)); + return result.option("-b, --browser ", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("--block-service-workers", "block service workers").option("--channel ", 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option("--color-scheme ", 'emulate preferred color scheme, "light" or "dark"').option("--device ", 'emulate device, for example "iPhone 11"').option("--geolocation ", 'specify geolocation coordinates, for example "37.819722,-122.478611"').option("--ignore-https-errors", "ignore https errors").option("--load-storage ", "load context storage state from the file, previously saved with --save-storage").option("--lang ", 'specify language / locale, for example "en-GB"').option("--proxy-server ", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--proxy-bypass ", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--save-har ", "save HAR file with all network activity at the end").option("--save-har-glob ", "filter entries in the HAR by matching url against this glob pattern").option("--save-storage ", "save context storage state at the end, for later use with --load-storage").option("--timezone