diff --git a/.github/workflows/qit-custom-test-woocommerce.yml b/.github/workflows/qit-custom-test-woocommerce.yml
new file mode 100644
index 00000000..043d35b9
--- /dev/null
+++ b/.github/workflows/qit-custom-test-woocommerce.yml
@@ -0,0 +1,89 @@
+name: QIT Custom Test - WooCommerce
+
+on:
+ # Manually
+ workflow_dispatch:
+
+jobs:
+ playwright-tests:
+ timeout-minutes: 60
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shardIndex: [1, 2, 3, 4]
+ shardTotal: [4]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ - name: Setup PHP (Cross-platform)
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.3'
+
+ - name: Enable dev mode
+ run: ./qit dev
+
+ # Delay start of tests based on shardIndex to space out requests and avoid 429.
+ # Example: This step causes shard 1 to start immediately, shard 2 to start after a 10-second delay, shard 3 after a 20-second delay, etc.
+ - name: Wait before running tests
+ run: sleep $(( (${{ matrix.shardIndex }} - 1) * 10 ))
+
+ - name: Connect to Staging QIT
+ run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com"
+
+ - name: Composer install
+ working-directory: src
+ run: composer install
+
+ - name: Start environment
+ working-directory: src
+ run: php qit-cli.php run:e2e https://github.com/woocommerce/woocommerce/releases/download/8.8.1/woocommerce.zip:test:./../_tests/example-tests/woocommerce -v --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
+
+ - name: Set the path in an env var
+ if: ${{ !cancelled() }}
+ working-directory: src
+ run: |
+ echo "BLOB_REPORT_PATH=$(php qit-cli.php e2e-report --dir_only)/blob" >> $GITHUB_ENV
+
+ - name: Upload blob report to GitHub Actions Artifacts
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: blob-report-${{ matrix.shardIndex }}
+ path: ${{ env.BLOB_REPORT_PATH }}
+ retention-days: 1
+
+ merge-reports:
+ # Merge reports after playwright-tests, even if some shards have failed
+ if: ${{ !cancelled() }}
+ needs: [playwright-tests]
+
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
+
+ - name: Install Playwright
+ run: npm install playwright
+
+ - name: Download blob reports from GitHub Actions Artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: all-blob-reports
+ pattern: blob-report-*
+ merge-multiple: true
+
+ - name: Merge into HTML Report
+ run: npx playwright merge-reports --reporter html ./all-blob-reports
+
+ - name: Upload HTML report
+ uses: actions/upload-artifact@v4
+ with:
+ name: html-report--attempt-${{ github.run_attempt }}
+ path: playwright-report
+ retention-days: 14
\ No newline at end of file
diff --git a/.github/workflows/qit-environment-dangling-test.yml b/.github/workflows/qit-environment-dangling-test.yml
index 1745e890..494f115f 100644
--- a/.github/workflows/qit-environment-dangling-test.yml
+++ b/.github/workflows/qit-environment-dangling-test.yml
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
jobs:
- environment_dangling_tests:
+ test_stop_random_container:
runs-on: ubuntu-latest
env:
NO_COLOR: 1
@@ -22,44 +22,62 @@ jobs:
with:
php-version: '8.3'
- - name: Composer install (Unix)
+ - name: Run setup script
+ run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com
+
+ - name: Environment Setup and Checks
+ id: env_setup
+ run: bash .github/workflows/tests/environments/start-and-assert.sh
+
+ - name: Store the environment path in a ENV var
working-directory: src
- run: composer install
+ run: echo "TEMPORARY_ENV=$(php qit-cli.php env:list --field=temporary_env)" >> $GITHUB_ENV
- - name: Enable dev mode
+ - name: Stop a random container from that environment.
working-directory: src
- run: php qit-cli.php dev
+ run: |
+ CONTAINERS=$(php qit-cli.php env:list --field=docker_images)
+ RANDOM_CONTAINER=$(echo $CONTAINERS | awk '{print $1}')
+ echo "Stopping container: $RANDOM_CONTAINER"
+ docker stop $RANDOM_CONTAINER
- - name: Connect to Staging QIT
+ - name: Run a env:list to trigger the Dangling Environment Cleanup
working-directory: src
- run: php qit-cli.php backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com"
+ run: php qit-cli.php env:list
- - name: Add "qit.test" to hosts file (Linux)
- run: sudo echo "127.0.0.1 qit.test" | sudo tee -a /etc/hosts
+ - name: Environment Cleanup Checks
+ id: env_cleanup
+ run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh
+
+ test_delete_environment_entry:
+ runs-on: ubuntu-latest
+ env:
+ NO_COLOR: 1
+ QIT_DISABLE_ONBOARDING: yes
+ steps:
+ - name: Checkout code (Cross-platform)
+ uses: actions/checkout@v4
- - name: Start environment
+ - name: Setup PHP (Cross-platform)
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.3'
+
+ - name: Run setup script
+ run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com
+
+ - name: Environment Setup and Checks
+ id: env_setup
+ run: bash .github/workflows/tests/environments/start-and-assert.sh
+
+ - name: Find and delete all hidden JSON files in the directory
working-directory: src
- run: php qit-cli.php env:up
+ run: php qit-cli.php cache delete environment_monitor
- - name: Test Site Up
- run: |
- # Fetch the site URL
- SITE_URL=$(php qit-cli.php env:list --field=site_url)
- echo "Site URL: $SITE_URL"
-
- # Assert home page is 200
- HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}\n" $SITE_URL)
- if [ "$HTTP_STATUS" -ne 200 ]; then
- echo "Home page is not up. HTTP status: $HTTP_STATUS"
- exit 1
- fi
- echo "Home page is up. HTTP status: $HTTP_STATUS"
-
- # Assert the name property in the JSON response
- JSON_RESPONSE=$(curl -s $SITE_URL/wp-json)
- NAME_PROPERTY=$(echo $JSON_RESPONSE | jq -r '.name')
- if [ "$NAME_PROPERTY" != "WooCommerce Core E2E Test Suite" ]; then
- echo "Name property does not match. Found: $NAME_PROPERTY"
- exit 1
- fi
- echo "Name property matches: $NAME_PROPERTY"
\ No newline at end of file
+ - name: Run a env:list to trigger the Dangling Environment Cleanup
+ working-directory: src
+ run: php qit-cli.php env:list
+
+ - name: Environment Cleanup Checks
+ id: env_cleanup
+ run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh
\ No newline at end of file
diff --git a/.github/workflows/qit-environment-test-linux.yml b/.github/workflows/qit-environment-test-linux.yml
index 27dd2c02..619d2d14 100644
--- a/.github/workflows/qit-environment-test-linux.yml
+++ b/.github/workflows/qit-environment-test-linux.yml
@@ -1,4 +1,4 @@
-name: QIT Environment Test - Linux and Mac
+name: QIT Environment Test - Linux
on:
# Every day at 11pm UTC (6pm ET)
@@ -8,19 +8,17 @@ on:
workflow_dispatch:
jobs:
- environment_tests:
+ linux_environment_test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ ubuntu-20.04, macos-13 ]
+ os: [ ubuntu-20.04 ]
php: [ '7.4' ]
wordpress: [ 'rc' ]
- woocommerce: [ 'rc' ]
include:
- os: ubuntu-20.04
php: '8.3'
wordpress: 'rc'
- woocommerce: 'rc'
fail-fast: false
env:
NO_COLOR: 1
@@ -34,26 +32,13 @@ jobs:
with:
php-version: '8.3'
- - name: Enable dev mode (Cross-platform)
+ - name: Enable dev mode
run: ./qit dev
- - name: Connect to Staging QIT (Cross-platform)
+ - name: Connect to Staging QIT
run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com"
- - name: Add "qit.test" to hosts file (Linux)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
- run: sudo echo "127.0.0.1 qit.test" | sudo tee -a /etc/hosts
-
- - name: Setup Docker on macOS (Mac)
- if: matrix.os == 'macos-13'
- uses: douglascamata/setup-docker-macos-action@v1-alpha
-
- - name: Check Colima Status (Mac)
- if: matrix.os == 'macos-13'
- run: colima status
-
- - name: Debug Docker and Docker Compose (Unix)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
+ - name: Debug Docker and Docker Compose
run: |
echo "Checking Docker version..."
docker --version || echo "Docker is not installed"
@@ -64,24 +49,19 @@ jobs:
echo "Checking Docker Compose (standalone) version..."
docker-compose --version || echo "Docker Compose (standalone) is not installed"
- - name: Benchmark Docker (Unix)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
+ - name: Benchmark Docker
working-directory: scripts
run: docker run -v $GITHUB_WORKSPACE/scripts:/scripts -w /scripts alpine sh -c "apk add --no-cache bash wget tar; chmod +x benchmark.sh; ./benchmark.sh"
- - name: Composer install (Unix)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
+ - name: Composer install
working-directory: src
run: composer install
- - name: Start environment (Unix)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
+ - name: Start environment
working-directory: src
- env:
- QIT_DOCKER_RUN_TIMEOUT: ${{ (matrix.os == 'ubuntu-20.04') && '300' || '1800' }}
- run: php qit-cli.php env:up --wordpress_version ${{ matrix.wordpress }} --woocommerce_version ${{ matrix.woocommerce }} --php_version ${{ matrix.php }}
+ run: php qit-cli.php env:up --wordpress_version ${{ matrix.wordpress }} --php_version ${{ matrix.php }}
- - name: Get URL (Cross-platform)
+ - name: Get URL
working-directory: src
run: |
siteUrl=$(php -r "echo exec(PHP_BINARY . ' qit-cli.php env:list --field=site_url');");
@@ -91,7 +71,7 @@ jobs:
run: |
php -r "echo 'Site URL: ' . getenv('SITE_URL');"
- - name: Test Site Up (Cross-platform)
+ - name: Test Site Up
run: |
php -r "
\$url = '${{ env.SITE_URL }}';
@@ -105,17 +85,15 @@ jobs:
exit(1);
}"
- - name: Query WP JSON (Cross-platform)
+ - name: Query WP JSON
run: php -r "echo file_get_contents('${{ env.SITE_URL }}/wp-json');"
- - name: Verify PHP Version (Unix)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
+ - name: Verify PHP Version
working-directory: src
run: |
actual_major_minor=$(docker exec $(php qit-cli.php env:list --field=docker_images | grep php) php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
[[ "$actual_major_minor" == "${{ matrix.php }}" ]] || { echo "PHP Major.Minor version mismatch. Expected: ${{ matrix.php }}, Actual: $actual_major_minor"; exit 1; }
- - name: Print WordPress Version (Unix)
- if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-13'
+ - name: Print WordPress Version
working-directory: src
run: php qit-cli.php env:exec "wp core version"
\ No newline at end of file
diff --git a/.github/workflows/qit-environment-test-mac.yml b/.github/workflows/qit-environment-test-mac.yml
new file mode 100644
index 00000000..7a7c3cd7
--- /dev/null
+++ b/.github/workflows/qit-environment-test-mac.yml
@@ -0,0 +1,109 @@
+##
+# ARM64 Macs such as M1/M2/M3 can't run Docker in CI, so we test only with older Macs.
+# More info: https://github.com/douglascamata/setup-docker-macos-action
+##
+name: QIT Environment Test - Mac (non M1)
+
+on:
+ # Every day at 11pm UTC (6pm ET)
+ schedule:
+ - cron: '0 23 * * *'
+ # Manually
+ workflow_dispatch:
+
+jobs:
+ mac_environment_test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ macos-13 ]
+ php: [ '7.4' ]
+ wordpress: [ 'rc' ]
+ fail-fast: false
+ env:
+ NO_COLOR: 1
+ QIT_DISABLE_ONBOARDING: yes
+ steps:
+ - name: Checkout code (Cross-platform)
+ uses: actions/checkout@v4
+
+ - name: Setup PHP (Cross-platform)
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.3'
+
+ - name: Enable dev mode
+ run: ./qit dev
+
+ - name: Connect to Staging QIT
+ run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com"
+
+ - name: Setup Docker on macOS
+ if: matrix.os == 'macos-13'
+ uses: douglascamata/setup-docker-macos-action@v1-alpha
+
+ - name: Check Colima Status (Mac)
+ if: matrix.os == 'macos-13'
+ run: colima status
+
+ - name: Debug Docker and Docker Compose
+ run: |
+ echo "Checking Docker version..."
+ docker --version || echo "Docker is not installed"
+
+ echo "Checking Docker Compose (plugin) version..."
+ docker compose version || echo "Docker Compose (plugin) is not installed"
+
+ echo "Checking Docker Compose (standalone) version..."
+ docker-compose --version || echo "Docker Compose (standalone) is not installed"
+
+ - name: Benchmark Docker
+ working-directory: scripts
+ run: docker run -v $GITHUB_WORKSPACE/scripts:/scripts -w /scripts alpine sh -c "apk add --no-cache bash wget tar; chmod +x benchmark.sh; ./benchmark.sh"
+
+ - name: Composer install
+ working-directory: src
+ run: composer install
+
+ - name: Start environment
+ working-directory: src
+ env:
+ QIT_DOCKER_RUN_TIMEOUT: 1800
+ run: php qit-cli.php env:up --wordpress_version ${{ matrix.wordpress }} --php_version ${{ matrix.php }}
+
+ - name: Get URL
+ working-directory: src
+ run: |
+ siteUrl=$(php -r "echo exec(PHP_BINARY . ' qit-cli.php env:list --field=site_url');");
+ echo "SITE_URL=$siteUrl" >> $GITHUB_ENV
+
+ - name: Print site URL
+ run: |
+ php -r "echo 'Site URL: ' . getenv('SITE_URL');"
+
+ - name: Test Site Up
+ run: |
+ php -r "
+ \$url = '${{ env.SITE_URL }}';
+ \$context = stream_context_create(['http' => ['method' => 'GET']]);
+ \$headers = @get_headers(\$url, 1, \$context);
+ \$statusCode = \$headers ? substr(\$headers[0], 9, 3) : 500;
+ if (\$statusCode == 200) {
+ echo 'Site is up';
+ } else {
+ echo 'Site is not up, status code: ' . \$statusCode;
+ exit(1);
+ }"
+
+ - name: Query WP JSON
+ run: php -r "echo file_get_contents('${{ env.SITE_URL }}/wp-json');"
+
+ - name: Verify PHP Version
+ working-directory: src
+ run: |
+ actual_major_minor=$(docker exec $(php qit-cli.php env:list --field=docker_images | grep php) php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
+ [[ "$actual_major_minor" == "${{ matrix.php }}" ]] || { echo "PHP Major.Minor version mismatch. Expected: ${{ matrix.php }}, Actual: $actual_major_minor"; exit 1; }
+
+ - name: Print WordPress Version
+ working-directory: src
+ run: php qit-cli.php env:exec "wp core version"
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-activation.yml b/.github/workflows/qit-self-test-activation.yml
index e773d203..3882c31d 100644
--- a/.github/workflows/qit-self-test-activation.yml
+++ b/.github/workflows/qit-self-test-activation.yml
@@ -19,4 +19,5 @@ jobs:
test_type: activation
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-custom-test.yml b/.github/workflows/qit-self-test-custom-test.yml
new file mode 100644
index 00000000..c3e9e20a
--- /dev/null
+++ b/.github/workflows/qit-self-test-custom-test.yml
@@ -0,0 +1,46 @@
+name: QIT Self-Tests - Custom Tests
+
+on:
+ # Every day at 11pm UTC (6pm ET)
+ schedule:
+ - cron: '0 23 * * *'
+ # Manually
+ workflow_dispatch:
+
+jobs:
+ self_test_custom_test:
+ runs-on: ubuntu-20.04
+ env:
+ NO_COLOR: 1
+ QIT_DISABLE_ONBOARDING: yes
+ QIT_DISABLE_CLEANUP: 1
+ QIT_SELF_TESTS: 1
+ steps:
+ - name: Checkout code (Cross-platform)
+ uses: actions/checkout@v4
+
+ - name: Create tmp directory on the workspace.
+ run: mkdir -p ${{ github.workspace }}/tmp
+
+ - name: Setup PHP (Cross-platform)
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.3'
+ ini-values: sys_temp_dir=${{ github.workspace }}/tmp
+
+ - name: Composer install
+ working-directory: _tests/custom_tests
+ run: composer install
+
+ - name: Fill in .env
+ working-directory: _tests/custom_tests
+ run: |
+ echo 'QIT_CUSTOM_TESTS_USER="noop"' >> .env
+ echo 'QIT_CUSTOM_TESTS_USER_QIT_TOKEN="noop"' >> .env
+ echo 'QIT_CUSTOM_TESTS_SECRET="${{ secrets.QIT_STAGING_SECRET }}"' >> .env
+ echo 'QIT_CUSTOM_TESTS_URL="https://stagingcompatibilitydashboard.wpcomstaging.com/"' >> .env
+ echo 'QIT_CUSTOM_TESTS_ENV="staging"' >> .env
+
+ - name: Run tests in parallel
+ working-directory: _tests/custom_tests
+ run: ./vendor/bin/phpunit
diff --git a/.github/workflows/qit-self-test-malware.yml b/.github/workflows/qit-self-test-malware.yml
index f4f1cf55..aaeaddf6 100644
--- a/.github/workflows/qit-self-test-malware.yml
+++ b/.github/workflows/qit-self-test-malware.yml
@@ -19,4 +19,5 @@ jobs:
test_type: malware
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-phpcompatibility.yml b/.github/workflows/qit-self-test-phpcompatibility.yml
index 8a915cec..a3f626bc 100644
--- a/.github/workflows/qit-self-test-phpcompatibility.yml
+++ b/.github/workflows/qit-self-test-phpcompatibility.yml
@@ -19,4 +19,5 @@ jobs:
test_type: phpcompatibility
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-phpstan.yml b/.github/workflows/qit-self-test-phpstan.yml
index 443f0f7d..5ee83a8d 100644
--- a/.github/workflows/qit-self-test-phpstan.yml
+++ b/.github/workflows/qit-self-test-phpstan.yml
@@ -19,4 +19,5 @@ jobs:
test_type: phpstan
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-security.yml b/.github/workflows/qit-self-test-security.yml
index 8845f993..2ea90002 100644
--- a/.github/workflows/qit-self-test-security.yml
+++ b/.github/workflows/qit-self-test-security.yml
@@ -19,4 +19,5 @@ jobs:
test_type: security
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-woo-api.yml b/.github/workflows/qit-self-test-woo-api.yml
index bf10001e..bfbb96f2 100644
--- a/.github/workflows/qit-self-test-woo-api.yml
+++ b/.github/workflows/qit-self-test-woo-api.yml
@@ -19,4 +19,5 @@ jobs:
test_type: woo-api
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-self-test-woo-e2e.yml b/.github/workflows/qit-self-test-woo-e2e.yml
index 348f0513..abe8c370 100644
--- a/.github/workflows/qit-self-test-woo-e2e.yml
+++ b/.github/workflows/qit-self-test-woo-e2e.yml
@@ -20,4 +20,5 @@ jobs:
test_type: woo-e2e
secrets:
QIT_USER: ${{ secrets.QIT_USER }}
- QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
\ No newline at end of file
+ QIT_ACCESS_TOKEN: ${{ secrets.QIT_ACCESS_TOKEN }}
+ QIT_STAGING_SECRET: ${{ secrets.QIT_STAGING_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/qit-windows.yml b/.github/workflows/qit-windows.yml
deleted file mode 100644
index 494f115f..00000000
--- a/.github/workflows/qit-windows.yml
+++ /dev/null
@@ -1,83 +0,0 @@
-name: QIT Environment Dangling Test
-
-on:
- # Every day at 11pm UTC (6pm ET)
- schedule:
- - cron: '0 23 * * *'
- # Manually
- workflow_dispatch:
-
-jobs:
- test_stop_random_container:
- runs-on: ubuntu-latest
- env:
- NO_COLOR: 1
- QIT_DISABLE_ONBOARDING: yes
- steps:
- - name: Checkout code (Cross-platform)
- uses: actions/checkout@v4
-
- - name: Setup PHP (Cross-platform)
- uses: shivammathur/setup-php@v2
- with:
- php-version: '8.3'
-
- - name: Run setup script
- run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com
-
- - name: Environment Setup and Checks
- id: env_setup
- run: bash .github/workflows/tests/environments/start-and-assert.sh
-
- - name: Store the environment path in a ENV var
- working-directory: src
- run: echo "TEMPORARY_ENV=$(php qit-cli.php env:list --field=temporary_env)" >> $GITHUB_ENV
-
- - name: Stop a random container from that environment.
- working-directory: src
- run: |
- CONTAINERS=$(php qit-cli.php env:list --field=docker_images)
- RANDOM_CONTAINER=$(echo $CONTAINERS | awk '{print $1}')
- echo "Stopping container: $RANDOM_CONTAINER"
- docker stop $RANDOM_CONTAINER
-
- - name: Run a env:list to trigger the Dangling Environment Cleanup
- working-directory: src
- run: php qit-cli.php env:list
-
- - name: Environment Cleanup Checks
- id: env_cleanup
- run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh
-
- test_delete_environment_entry:
- runs-on: ubuntu-latest
- env:
- NO_COLOR: 1
- QIT_DISABLE_ONBOARDING: yes
- steps:
- - name: Checkout code (Cross-platform)
- uses: actions/checkout@v4
-
- - name: Setup PHP (Cross-platform)
- uses: shivammathur/setup-php@v2
- with:
- php-version: '8.3'
-
- - name: Run setup script
- run: bash .github/workflows/tests/environments/setup.sh ${{ secrets.QIT_STAGING_SECRET }} https://stagingcompatibilitydashboard.wpcomstaging.com
-
- - name: Environment Setup and Checks
- id: env_setup
- run: bash .github/workflows/tests/environments/start-and-assert.sh
-
- - name: Find and delete all hidden JSON files in the directory
- working-directory: src
- run: php qit-cli.php cache delete environment_monitor
-
- - name: Run a env:list to trigger the Dangling Environment Cleanup
- working-directory: src
- run: php qit-cli.php env:list
-
- - name: Environment Cleanup Checks
- id: env_cleanup
- run: bash .github/workflows/tests/environments/dangling-cleanup-assert.sh
\ No newline at end of file
diff --git a/.github/workflows/self-test-template.yml b/.github/workflows/self-test-template.yml
index bb92fe06..f75a80c8 100644
--- a/.github/workflows/self-test-template.yml
+++ b/.github/workflows/self-test-template.yml
@@ -12,6 +12,8 @@ on:
required: true
QIT_ACCESS_TOKEN:
required: true
+ QIT_STAGING_SECRET:
+ required: true
jobs:
self_test:
@@ -57,11 +59,17 @@ jobs:
php-version: '7.2'
- name: Composer install
- working-directory: _tests
+ working-directory: _tests/managed_tests
run: composer install
- - name: Connect to QIT
- run: ./qit partner:add --user="${{ secrets.QIT_USER }}" --application_password="${{ secrets.QIT_ACCESS_TOKEN }}"
+ #- name: Connect to QIT
+ # run: ./qit partner:add --user="${{ secrets.QIT_USER }}" --application_password="${{ secrets.QIT_ACCESS_TOKEN }}"
+
+ - name: Enable dev (Staging)
+ run: ./qit dev
+
+ - name: Connect to QIT (Staging)
+ run: ./qit backend:add --environment="staging" --qit_secret="${{ secrets.QIT_STAGING_SECRET }}" --manager_url="https://stagingcompatibilitydashboard.wpcomstaging.com"
### Cache ZIP Image
- name: Set up date
@@ -88,7 +96,7 @@ jobs:
- name: Run Self-Tests
id: run-self-tests
- working-directory: _tests
+ working-directory: _tests/managed_tests
env:
QIT_WAIT_BEFORE_REQUEST: yes
run: php ./QITSelfTests.php run ${{ inputs.test_type }}
diff --git a/.gitignore b/.gitignore
index 3ad22315..a7786d22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,15 +14,19 @@ project.properties
.sublimelinterrc
vendor/
-!_tests/phpstan/with_vendor/woocommerce-product-feeds/vendor
+!_tests/managed_tests/phpstan/with_vendor/woocommerce-product-feeds/vendor
.idea/
src-tmp/
.cache
.phpunit.result.cache
./qit
-_tests/**/*.zip
-_tests/**/*.txt
+_tests/managed_tests/**/*.zip
+_tests/managed_tests/**/*.txt
dev/
_build/docker/php83/xdebug/*
!_build/docker/php83/xdebug/.gitkeep
-!_build/docker/php83/xdebug/README.md
\ No newline at end of file
+!_build/docker/php83/xdebug/README.md
+_tests/custom_tests/.env
+_tests/custom_tests/tmp_qit_config*
+_tests/custom_tests/cache
+_tests/custom_tests/tmp/
diff --git a/Makefile b/Makefile
index 7559873f..da06fcc8 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,7 @@ phpcbf:
$(call execPhpAlpine,/app/src/vendor/bin/phpcbf /app/src/qit-cli.php /app/src/src -s --standard=/app/src/.phpcs.xml.dist)
phpcs:
- $(MAKE) phpcbf
+ $(MAKE) phpcbf || true
$(call execPhpAlpine,/app/src/vendor/bin/phpcs /app/src/qit-cli.php /app/src/src -s --standard=/app/src/.phpcs.xml.dist)
phpstan:
diff --git a/_tests/custom_tests/.env.sample b/_tests/custom_tests/.env.sample
new file mode 100644
index 00000000..2147dcbf
--- /dev/null
+++ b/_tests/custom_tests/.env.sample
@@ -0,0 +1,14 @@
+# The username of the partner being used.
+QIT_CUSTOM_TESTS_USER=""
+
+# The token of the partner being used.
+QIT_CUSTOM_TESTS_USER_QIT_TOKEN=""
+
+# The Secret of the environment.
+QIT_CUSTOM_TESTS_SECRET=""
+
+# The URL to the QIT Manager being used (URL of the Environment).
+QIT_CUSTOM_TESTS_URL=""
+
+# The environment, eg: "staging", "local", "production".
+QIT_CUSTOM_TESTS_ENV=""
\ No newline at end of file
diff --git a/_tests/custom_tests/QITTestExtension.php b/_tests/custom_tests/QITTestExtension.php
new file mode 100644
index 00000000..ab24a89d
--- /dev/null
+++ b/_tests/custom_tests/QITTestExtension.php
@@ -0,0 +1,289 @@
+registerSubscriber( new \QITTestStart() );
+ $facade->registerSubscriber( new \QITTestFinish() );
+ }
+}
+
+class QITTestStart implements ExecutionStartedSubscriber {
+ public function notify( ExecutionStarted $event ): void {
+ if ( file_exists( __DIR__ . '/qit-env.json' ) ) {
+ unlink( __DIR__ . '/qit-env.json' );
+ echo 'Deleted qit-env.json';
+ }
+
+ $dotenv = Dotenv::createImmutable( __DIR__ );
+ $dotenv->load();
+ $dotenv->required( [
+ 'QIT_CUSTOM_TESTS_USER',
+ 'QIT_CUSTOM_TESTS_USER_QIT_TOKEN',
+ 'QIT_CUSTOM_TESTS_SECRET',
+ 'QIT_CUSTOM_TESTS_URL',
+ 'QIT_CUSTOM_TESTS_ENV',
+ ] );
+
+ if ( ! file_exists( __DIR__ . '/../../qit' ) ) {
+ throw new \RuntimeException( sprintf( 'The qit binary was not found at %s.', realpath( __DIR__ . '/../../qit' ) ) );
+ }
+
+ $GLOBALS['qit'] = __DIR__ . '/../../qit';
+
+ // Generate an ID for this run.
+ $run_id = uniqid( 'qit_custom_tests_' );
+ $qit_tmp_dir = __DIR__ . "/tmp/tmp_qit_config-$run_id";
+
+ $GLOBALS['QIT_HOME'] = $qit_tmp_dir;
+ $GLOBALS['RUN_ID'] = $run_id;
+
+ $fs = new Filesystem();
+
+ // We utilize the filesystem as shared state to coordinate parallel processes.
+ if ( ! touch( sys_get_temp_dir() . '/test-initialization-lock-file' ) ) {
+ throw new \RuntimeException( 'Failed to create lock file at ' . sys_get_temp_dir() . '/test-initialization-lock-file' );
+ }
+
+ $lock_file = fopen( sys_get_temp_dir() . '/test-initialization-lock-file', 'w+' );
+
+ if ( ! $lock_file ) {
+ throw new \RuntimeException( 'Failed to open lock file at ' . sys_get_temp_dir() . '/test-initialization-lock-file' );
+ }
+
+ // Attempt to get an exclusive lock - first process wins.
+ if ( flock( $lock_file, LOCK_EX | LOCK_NB ) ) {
+ echo sprintf( "Process %s has exclusive lock\n", getenv( 'TEST_TOKEN' ) );
+ // Since we are the single process that has an exclusive lock, we run the initialization.
+
+ // Cleanup initial state.
+ array_map( 'unlink', glob( sys_get_temp_dir() . '/qit-running-*' ) );
+ array_map( 'unlink', glob( sys_get_temp_dir() . '/qit-test-tag-lock-*' ) );
+ if ( file_exists( sys_get_temp_dir() . '/qit-semaphore' ) ) {
+ unlink( sys_get_temp_dir() . '/qit-semaphore' );
+ }
+ // Delete all directories in the current dir that matches the pattern "tmp_qit_config-*"
+ if ( file_exists( __DIR__ . '/tmp/' ) ) {
+ try {
+ $fs->remove( __DIR__ . '/tmp/' );
+ } catch ( \Exception $e ) {
+ /**
+ * Try to delete twice, waiting a bit before the second retry, as during
+ * code review an issue popped up on Mac when deleting resource fork files.
+ * @link https://github.com/woocommerce/qit-cli/pull/148#issuecomment-2096920576
+ */
+ sleep( 5 );
+ $fs->remove( __DIR__ . '/tmp/' );
+ }
+ }
+
+ if ( ! mkdir( __DIR__ . '/tmp', 0755, true ) ) {
+ throw new \RuntimeException( 'Failed to create the tmp directory.' );
+ }
+
+ // Enable dev mode.
+ $dev = new Process( [ $GLOBALS['qit'], 'dev' ] );
+ $dev->setEnv( [
+ 'QIT_HOME' => $GLOBALS['QIT_HOME'],
+ ] );
+ $dev->setTimeout( 30 );
+ $dev->setIdleTimeout( 30 );
+ $dev->mustRun( function ( $type, $buffer ) {
+ echo $buffer;
+ } );
+
+ // Add the environment.
+ $add_environment = new Process( [
+ $GLOBALS['qit'],
+ 'backend:add',
+ '--manager_url',
+ $_ENV['QIT_CUSTOM_TESTS_URL'],
+ '--qit_secret',
+ $_ENV['QIT_CUSTOM_TESTS_SECRET'],
+ '--environment',
+ $_ENV['QIT_CUSTOM_TESTS_ENV'],
+ ] );
+ $add_environment->setEnv( [ 'QIT_HOME' => $GLOBALS['QIT_HOME'] ] );
+ $add_environment->mustRun( function ( $type, $buffer ) {
+ echo $buffer;
+ } );
+
+ if ( $_ENV['QIT_CUSTOM_TESTS_ENV'] !== 'staging' ) {
+ // Add the partner account that will be used.
+ $add_partner = new Process( [
+ $GLOBALS['qit'],
+ 'partner:add',
+ '--user',
+ $_ENV['QIT_CUSTOM_TESTS_USER'],
+ '--qit_token',
+ $_ENV['QIT_CUSTOM_TESTS_USER_QIT_TOKEN'],
+ ] );
+ $add_partner->setEnv( [ 'QIT_HOME' => $GLOBALS['QIT_HOME'] ] );
+ $add_partner->mustRun( function ( $type, $buffer ) {
+ echo $buffer;
+ } );
+ } else {
+ echo "Skipping partner add for staging environment\n";
+ }
+
+ // Validate connection. Run "qit extensions" and assert "18734003134382" is present, which is the ID of the "woocommerce" extension.
+ $extensions = new Process( [ $GLOBALS['qit'], 'extensions' ] );
+ $extensions->setEnv( [ 'QIT_HOME' => $GLOBALS['QIT_HOME'] ] );
+ $extensions->mustRun();
+
+ $woocommerce_ids_per_environment = [
+ 'staging' => 18734003134382,
+ 'local' => 18734002449992,
+ 'production' => 18734002449992,
+ ];
+
+ $woocommerce_id = $woocommerce_ids_per_environment[ $_ENV['QIT_CUSTOM_TESTS_ENV'] ];
+
+ if ( strpos( $extensions->getOutput(), $woocommerce_id ) === false ) {
+ if ( ! fwrite( $lock_file, 'failed' ) ) {
+ throw new \RuntimeException( 'Failed to write to lock file.' );
+ }
+ } else {
+ if ( ! fwrite( $lock_file, $GLOBALS['QIT_HOME'] ) ) {
+ throw new \RuntimeException( 'Failed to write to lock file.' );
+ }
+ }
+
+ if ( ! file_exists( __DIR__ . '/cache' ) ) {
+ mkdir( __DIR__ . '/cache' );
+ }
+
+ if ( ! file_exists( __DIR__ . '/tmp' ) ) {
+ mkdir( __DIR__ . '/tmp' );
+ }
+
+ $fs->mirror( __DIR__ . '/cache', $GLOBALS['QIT_HOME'] . '/cache' );
+
+ $GLOBALS['IS_SOURCE'] = true;
+
+ echo "Main process has authenticated and is releasing the lock...\n";
+
+ flock( $lock_file, LOCK_UN );
+ fclose( $lock_file );
+
+ // Wait after unlocking so that other processes can copy this source directory.
+ sleep( 10 );
+ } else {
+ if ( ! touch( sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" ) ) {
+ throw new \RuntimeException( 'Failed to create running file at ' . sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" );
+ }
+
+ // If no exclusive lock is available, block until the first process is done with initialization
+ flock( $lock_file, LOCK_SH );
+
+ echo sprintf( "Process %s proceeding after the lock is released\n", getenv( 'TEST_TOKEN' ) );
+
+ // Read the contents of the lock file to get the QIT_HOME directory.
+ $source_qit_home = fread( $lock_file, 1024 );
+ fclose( $lock_file );
+
+ if ( $source_qit_home === 'failed' ) {
+ throw new \RuntimeException( 'Bailing because it failed to initialize.' );
+ }
+
+ if ( ! file_exists( $source_qit_home ) ) {
+ throw new \RuntimeException( sprintf( 'The QIT_HOME directory "%s" does not exist.', $source_qit_home ) );
+ }
+
+ $GLOBALS['QIT_SOURCE_DIR'] = $source_qit_home;
+
+ // Mirror the directory.
+ $fs->mirror( $source_qit_home, $GLOBALS['QIT_HOME'] );
+ }
+
+ // Make sure each parallel process is spaced out a little bit.
+ $semaphore = sys_get_temp_dir() . '/qit-semaphore.log';
+ $fp = fopen( $semaphore, 'c+' );
+ if ( ! $fp ) {
+ throw new \RuntimeException( "Failed to open semaphore file. $semaphore" );
+ }
+ $start = microtime( true );
+ if ( flock( $fp, LOCK_EX ) ) {
+ if ( empty( fread( $fp, 1 ) ) ) {
+ // First process.
+ file_put_contents( $semaphore, sprintf( "[%s - Microtime: %s] First process\n", getenv( 'TEST_TOKEN' ), microtime() ) );
+ } else {
+ // Lock acquired, now wait 5 seconds between each request.
+ $sleep = 5000000;
+ file_put_contents( $semaphore, sprintf( "[%s - Microtime: %s], Sleeping for $sleep microseconds\n", getenv( 'TEST_TOKEN' ), microtime() ), FILE_APPEND );
+ usleep( $sleep );
+ file_put_contents( $semaphore, sprintf( "[%s - Microtime: %s] Finished sleeping (Waited %s total)\n", getenv( 'TEST_TOKEN' ), microtime(), microtime( true ) - $start ), FILE_APPEND );
+ }
+ flock( $fp, LOCK_UN );
+ }
+
+ fclose( $fp );
+ }
+}
+
+class QITTestFinish implements ExecutionFinishedSubscriber {
+ public function notify( ExecutionFinished $event ): void {
+ if ( getenv( 'CI' ) ) {
+ echo "Skipping cleanup because this is a CI environment.\n";
+ }
+
+ if ( ! isset( $GLOBALS['RUN_ID'] ) ) {
+ throw new \RuntimeException( 'The "RUN_ID" GLOBAL must be set.' );
+ }
+
+ if ( file_exists( sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" ) ) {
+ unlink( sys_get_temp_dir() . "/qit-running-{$GLOBALS['RUN_ID']}" );
+ }
+
+ self::delete_temp_environment();
+ }
+
+ public static function delete_temp_environment(): void {
+ if ( empty( $GLOBALS['QIT_HOME'] ) || empty( $GLOBALS['qit'] ) ) {
+ throw new \LogicException( 'The "QIT_HOME" and "qit" GLOBALS must be set.' );
+ }
+
+ $fs = new Filesystem();
+
+ // Check that $GLOBALS['QIT_HOME'] is inside this directory.
+ if ( strpos( $GLOBALS['QIT_HOME'], __DIR__ ) !== 0 ) {
+ throw new \RuntimeException( sprintf( 'The QIT_HOME directory is not inside the test directory. This is a security risk. QIT_HOME: %s, Test Directory: %s', $GLOBALS['QIT_HOME'], __DIR__ ) );
+ }
+
+ if ( file_exists( __DIR__ . '/qit-env.json' ) ) {
+ unlink( __DIR__ . '/qit-env.json' );
+ }
+
+ if ( $fs->exists( $GLOBALS['QIT_HOME'] ) ) {
+ // Copy files from $GLOBALS['QIT_HOME'] . '/cache' to __DIR__. '/cache'
+ if ( file_exists( $GLOBALS['QIT_HOME'] . '/cache' ) ) {
+ $fs->mirror( $GLOBALS['QIT_HOME'] . '/cache', __DIR__ . '/cache' );
+ }
+
+ if ( isset( $GLOBALS['IS_SOURCE'] ) && $GLOBALS['IS_SOURCE'] ) {
+ $timeout = 300;
+ // Wait for all other tests to finish.
+ while ( count( glob( sys_get_temp_dir() . '/qit-running-*' ) ) > 1 ) {
+ echo sprintf( "Main process is waiting for %d other tests to finish.\n", count( glob( sys_get_temp_dir() . '/qit-running-*' ) ) - 1 );
+ sleep( 5 );
+ $timeout -= 5;
+ if ( $timeout <= 0 ) {
+ throw new \RuntimeException( 'Timeout waiting for other tests to finish.' );
+ }
+ }
+ }
+ $fs->remove( $GLOBALS['QIT_HOME'] );
+ }
+ }
+}
\ No newline at end of file
diff --git a/_tests/custom_tests/README.md b/_tests/custom_tests/README.md
new file mode 100644
index 00000000..c1178a92
--- /dev/null
+++ b/_tests/custom_tests/README.md
@@ -0,0 +1,24 @@
+### Running tests
+
+1. Copy `.env.example` to `.env` and update the values
+2. Run `./vendor/bin/paratest`
+
+### Updating snapshots
+
+1. Append "UPDATE_SNAPSHOTS" env var, eg: `UPDATE_SNAPSHOTS=true ./vendor/bin/paratest`
+
+### How it works
+
+1. The tests are regular PHPUnit tests
+2. The tests run in parallel using [ParaTest](https://github.com/paratestphp/paratest)
+3. This is because each test might spin up a QIT environment, which takes some time
+4. If the tests were run sequentially, it would take a long time to run all the tests
+5. For each test, we:
+ - Assign a uniquely generated QIT_HOME
+ - The first test that runs will acquire a exclusive file lock on a file
+ - All other tests when seeing that there is a file lock on a file will wait for that lock to be released
+ - The first test will authenticate to QIT and release the lock
+ - All other tests will then reuse the same QIT baseline authentication JSON
+ - Then all tests starts in parallel, with a shared, persistent cache. Ideally, all operations would be offline, without touching the internet
+ - This allows us to write more and more tests without increasing the time linearly
+ - After the test ends, each QIT_HOME that was generated is deleted
\ No newline at end of file
diff --git a/_tests/custom_tests/bootstrap.php b/_tests/custom_tests/bootstrap.php
new file mode 100644
index 00000000..e895e5eb
--- /dev/null
+++ b/_tests/custom_tests/bootstrap.php
@@ -0,0 +1,80 @@
+scaffold_test(),
+ * ] );
+ * Lock "automatewoo:self-test-scaffolded"
+ * When we receive qit( [ 'tag:delete', 'automatewoo:self-test-scaffolded' ] ) we unlock it.
+ *
+ * If we receive another command like the previous one while it's locked, wait.
+ */
+ if ( 'tag:upload' === $command[0] ) {
+ $lock_name = $command[1];
+ $lock_file = sprintf( '%s/qit-test-tag-lock-%s', sys_get_temp_dir(), md5( $lock_name ) );
+ $max_wait = 60;
+ $waited = 0;
+ while ( file_exists( $lock_file ) ) {
+ sleep( 1 );
+ $waited ++;
+ if ( $waited > $max_wait ) {
+ throw new \RuntimeException( sprintf( 'Timeout while waiting for the lock for %s', $lock_name ) );
+ }
+ }
+ touch( $lock_file );
+ } elseif ( 'tag:delete' === $command[0] ) {
+ $lock_name = $command[1];
+ $lock_file = sprintf( '%s/qit-test-tag-lock-%s', sys_get_temp_dir(), md5( $lock_name ) );
+ if ( file_exists( $lock_file ) ) {
+ unlink( $lock_file );
+ }
+ }
+
+ $args = [ $GLOBALS['qit'] ];
+ $args = array_merge( $args, $command );
+ if ( ! empty( $qit_env_json ) ) {
+ $args[] = '--config';
+ $args[] = $qit_config_filename;
+ }
+ $qit = new Process( $args );
+ $qit->setTimeout( 300 );
+ $qit->setIdleTimeout( 300 );
+ $qit->setTty( false );
+ $qit->setPty( false );
+ $qit->setEnv( [
+ 'QIT_HOME' => $GLOBALS['QIT_HOME'],
+ 'QIT_DISABLE_CLEANUP' => '1', // We need to disable it because of parallelization with individualized QIT_HOMEs.
+ 'QIT_SELF_TESTS' => '1',
+ 'CI' => '1',
+ 'COLUMNS' => '300', // Set a fixed width so that we can snapshot the output.
+ ] );
+ $qit->run();
+
+ if ( $qit->getExitCode() !== $expected_exit_code ) {
+ throw new \RuntimeException( sprintf( "Command \"%s\" failed with exit code %d. \n\nError Output:\n %s \n\nOutput:\n %s", implode( ' ', $command ), $qit->getExitCode(), $qit->getErrorOutput(), $qit->getOutput() ) );
+ }
+
+ return $qit->getOutput();
+}
\ No newline at end of file
diff --git a/_tests/custom_tests/composer.json b/_tests/custom_tests/composer.json
new file mode 100644
index 00000000..646b8834
--- /dev/null
+++ b/_tests/custom_tests/composer.json
@@ -0,0 +1,26 @@
+{
+ "config": {
+ "platform": {
+ "php": "8.2"
+ }
+ },
+ "require": {
+ "php": "^8"
+ },
+ "autoload": {
+ "psr-4": {
+ "QIT\\SelfTests\\CustomTests\\": "tests"
+ },
+ "files": [
+ "QITTestExtension.php"
+ ]
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11",
+ "spatie/phpunit-snapshot-assertions": "^5.0",
+ "symfony/process": "^7",
+ "symfony/filesystem": "^7",
+ "vlucas/phpdotenv": "^5",
+ "brianium/paratest": "^7"
+ }
+}
diff --git a/_tests/custom_tests/composer.lock b/_tests/custom_tests/composer.lock
new file mode 100644
index 00000000..48c48ea2
--- /dev/null
+++ b/_tests/custom_tests/composer.lock
@@ -0,0 +1,3328 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "d650fa6759bc5a32508668df20938212",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "brianium/paratest",
+ "version": "v7.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paratestphp/paratest.git",
+ "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec",
+ "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-simplexml": "*",
+ "fidry/cpu-core-counter": "^1.1.0",
+ "jean85/pretty-package-versions": "^2.0.5",
+ "php": "~8.2.0 || ~8.3.0",
+ "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0",
+ "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0",
+ "phpunit/php-timer": "^6.0.0 || ^7.0.0",
+ "phpunit/phpunit": "^10.5.9 || ^11.0.3",
+ "sebastian/environment": "^6.0.1 || ^7.0.0",
+ "symfony/console": "^6.4.3 || ^7.0.3",
+ "symfony/process": "^6.4.3 || ^7.0.3"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^12.0.0",
+ "ext-pcov": "*",
+ "ext-posix": "*",
+ "phpstan/phpstan": "^1.10.58",
+ "phpstan/phpstan-deprecation-rules": "^1.1.4",
+ "phpstan/phpstan-phpunit": "^1.3.15",
+ "phpstan/phpstan-strict-rules": "^1.5.2",
+ "squizlabs/php_codesniffer": "^3.9.0",
+ "symfony/filesystem": "^6.4.3 || ^7.0.3"
+ },
+ "bin": [
+ "bin/paratest",
+ "bin/paratest.bat",
+ "bin/paratest_for_phpstorm"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ParaTest\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian Scaturro",
+ "email": "scaturrob@gmail.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Filippo Tessarotto",
+ "email": "zoeslam@gmail.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "Parallel testing for PHP",
+ "homepage": "https://github.com/paratestphp/paratest",
+ "keywords": [
+ "concurrent",
+ "parallel",
+ "phpunit",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/paratestphp/paratest/issues",
+ "source": "https://github.com/paratestphp/paratest/tree/v7.4.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/Slamdunk",
+ "type": "github"
+ },
+ {
+ "url": "https://paypal.me/filippotessarotto",
+ "type": "paypal"
+ }
+ ],
+ "time": "2024-02-20T07:24:02+00:00"
+ },
+ {
+ "name": "fidry/cpu-core-counter",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theofidry/cpu-core-counter.git",
+ "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42",
+ "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "fidry/makefile": "^0.2.0",
+ "fidry/php-cs-fixer-config": "^1.1.2",
+ "phpstan/extension-installer": "^1.2.0",
+ "phpstan/phpstan": "^1.9.2",
+ "phpstan/phpstan-deprecation-rules": "^1.0.0",
+ "phpstan/phpstan-phpunit": "^1.2.2",
+ "phpstan/phpstan-strict-rules": "^1.4.4",
+ "phpunit/phpunit": "^8.5.31 || ^9.5.26",
+ "webmozarts/strict-phpunit": "^7.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Fidry\\CpuCoreCounter\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Théo FIDRY",
+ "email": "theo.fidry@gmail.com"
+ }
+ ],
+ "description": "Tiny utility to get the number of CPU cores.",
+ "keywords": [
+ "CPU",
+ "core"
+ ],
+ "support": {
+ "issues": "https://github.com/theofidry/cpu-core-counter/issues",
+ "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theofidry",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-07T09:43:46+00:00"
+ },
+ {
+ "name": "graham-campbell/result-type",
+ "version": "v1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GrahamCampbell/Result-Type.git",
+ "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
+ "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "GrahamCampbell\\ResultType\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "An Implementation Of The Result Type",
+ "keywords": [
+ "Graham Campbell",
+ "GrahamCampbell",
+ "Result Type",
+ "Result-Type",
+ "result"
+ ],
+ "support": {
+ "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-11-12T22:16:48+00:00"
+ },
+ {
+ "name": "jean85/pretty-package-versions",
+ "version": "2.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Jean85/pretty-package-versions.git",
+ "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4",
+ "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4",
+ "shasum": ""
+ },
+ "require": {
+ "composer-runtime-api": "^2.0.0",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.2",
+ "jean85/composer-provided-replaced-stub-package": "^1.0",
+ "phpstan/phpstan": "^1.4",
+ "phpunit/phpunit": "^7.5|^8.5|^9.4",
+ "vimeo/psalm": "^4.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Jean85\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alessandro Lai",
+ "email": "alessandro.lai85@gmail.com"
+ }
+ ],
+ "description": "A library to get pretty versions strings of installed dependencies",
+ "keywords": [
+ "composer",
+ "package",
+ "release",
+ "versions"
+ ],
+ "support": {
+ "issues": "https://github.com/Jean85/pretty-package-versions/issues",
+ "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6"
+ },
+ "time": "2024-03-08T09:58:59+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-03-08T13:26:56+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
+ "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
+ },
+ "time": "2024-03-05T20:51:40+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpoption/phpoption",
+ "version": "1.9.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/schmittjoh/php-option.git",
+ "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
+ "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": true
+ },
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpOption\\": "src/PhpOption/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Johannes M. Schmitt",
+ "email": "schmittjoh@gmail.com",
+ "homepage": "https://github.com/schmittjoh"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "Option Type for PHP",
+ "keywords": [
+ "language",
+ "option",
+ "php",
+ "type"
+ ],
+ "support": {
+ "issues": "https://github.com/schmittjoh/php-option/issues",
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-11-12T21:59:55+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "11.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e35a2cbcabac0e6865fd373742ea432a3c34f92",
+ "reference": "7e35a2cbcabac0e6865fd373742ea432a3c34f92",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.0",
+ "phpunit/php-text-template": "^4.0",
+ "sebastian/code-unit-reverse-lookup": "^4.0",
+ "sebastian/complexity": "^4.0",
+ "sebastian/environment": "^7.0",
+ "sebastian/lines-of-code": "^3.0",
+ "sebastian/version": "^5.0",
+ "theseer/tokenizer": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-12T15:35:40+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "99e95c94ad9500daca992354fa09d7b99abe2210"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/99e95c94ad9500daca992354fa09d7b99abe2210",
+ "reference": "99e95c94ad9500daca992354fa09d7b99abe2210",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:05:04+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5d8d9355a16d8cc5a1305b0a85342cfa420612be",
+ "reference": "5d8d9355a16d8cc5a1305b0a85342cfa420612be",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:05:50+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/d38f6cbff1cdb6f40b03c9811421561668cc133e",
+ "reference": "d38f6cbff1cdb6f40b03c9811421561668cc133e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:06:56+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "7.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8a59d9e25720482ee7fcdf296595e08795b84dc5",
+ "reference": "8a59d9e25720482ee7fcdf296595e08795b84dc5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:08:01+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "11.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "d475be032238173ca3b0a516f5cc291d174708ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d475be032238173ca3b0a516f5cc291d174708ae",
+ "reference": "d475be032238173ca3b0a516f5cc291d174708ae",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.3",
+ "phar-io/version": "^3.0.2",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0",
+ "phpunit/php-file-iterator": "^5.0",
+ "phpunit/php-invoker": "^5.0",
+ "phpunit/php-text-template": "^4.0",
+ "phpunit/php-timer": "^7.0",
+ "sebastian/cli-parser": "^3.0",
+ "sebastian/code-unit": "^3.0",
+ "sebastian/comparator": "^6.0",
+ "sebastian/diff": "^6.0",
+ "sebastian/environment": "^7.0",
+ "sebastian/exporter": "^6.0",
+ "sebastian/global-state": "^7.0",
+ "sebastian/object-enumerator": "^6.0",
+ "sebastian/type": "^5.0",
+ "sebastian/version": "^5.0"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.1-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-04-24T06:34:25+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "00a74d5568694711f0222e54fb281e1d15fdf04a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/00a74d5568694711f0222e54fb281e1d15fdf04a",
+ "reference": "00a74d5568694711f0222e54fb281e1d15fdf04a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:26:58+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "6634549cb8d702282a04a774e36a7477d2bd9015"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6634549cb8d702282a04a774e36a7477d2bd9015",
+ "reference": "6634549cb8d702282a04a774e36a7477d2bd9015",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T05:50:41+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/df80c875d3e459b45c6039e4d9b71d4fbccae25d",
+ "reference": "df80c875d3e459b45c6039e4d9b71d4fbccae25d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T05:52:17+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bd0f2fa5b9257c69903537b266ccb80fcf940db8",
+ "reference": "bd0f2fa5b9257c69903537b266ccb80fcf940db8",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T05:53:45+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "88a434ad86150e11a606ac4866b09130712671f0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/88a434ad86150e11a606ac4866b09130712671f0",
+ "reference": "88a434ad86150e11a606ac4866b09130712671f0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T05:55:19+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "ab83243ecc233de5655b76f577711de9f842e712"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ab83243ecc233de5655b76f577711de9f842e712",
+ "reference": "ab83243ecc233de5655b76f577711de9f842e712",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:30:33+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "7.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "4eb3a442574d0e9d141aab209cd4aaf25701b09a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4eb3a442574d0e9d141aab209cd4aaf25701b09a",
+ "reference": "4eb3a442574d0e9d141aab209cd4aaf25701b09a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/7.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-23T08:56:34+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "f291e5a317c321c0381fa9ecc796fa2d21b186da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f291e5a317c321c0381fa9ecc796fa2d21b186da",
+ "reference": "f291e5a317c321c0381fa9ecc796fa2d21b186da",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:28:20+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "c3a307e832f2e69c7ef869e31fc644fde0e7cb3e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c3a307e832f2e69c7ef869e31fc644fde0e7cb3e",
+ "reference": "c3a307e832f2e69c7ef869e31fc644fde0e7cb3e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:32:10+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/376c5b3f6b43c78fdc049740bca76a7c846706c0",
+ "reference": "376c5b3f6b43c78fdc049740bca76a7c846706c0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:00:36+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f75f6c460da0bbd9668f43a3dde0ec0ba7faa678",
+ "reference": "f75f6c460da0bbd9668f43a3dde0ec0ba7faa678",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:01:29+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/bb2a6255d30853425fd38f032eb64ced9f7f132d",
+ "reference": "bb2a6255d30853425fd38f032eb64ced9f7f132d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:02:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b75224967b5a466925c6d54e68edd0edf8dd4ed4",
+ "reference": "b75224967b5a466925c6d54e68edd0edf8dd4ed4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:08:48+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8502785eb3523ca0dd4afe9ca62235590020f3f",
+ "reference": "b8502785eb3523ca0dd4afe9ca62235590020f3f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:09:34+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/13999475d2cb1ab33cb73403ba356a814fdbb001",
+ "reference": "13999475d2cb1ab33cb73403ba356a814fdbb001",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-02T06:10:47+00:00"
+ },
+ {
+ "name": "spatie/phpunit-snapshot-assertions",
+ "version": "5.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/phpunit-snapshot-assertions.git",
+ "reference": "fe84650c4cfb8f51ecf5146f4089369fafea2c94"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/phpunit-snapshot-assertions/zipball/fe84650c4cfb8f51ecf5146f4089369fafea2c94",
+ "reference": "fe84650c4cfb8f51ecf5146f4089369fafea2c94",
+ "shasum": ""
+ },
+ "require": {
+ "composer-runtime-api": "^2.0",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "php": "^8.1",
+ "phpunit/phpunit": "^10.0|^11.0",
+ "symfony/property-access": "^5.2|^6.2|^7.0",
+ "symfony/serializer": "^5.2|^6.2|^7.0",
+ "symfony/yaml": "^5.2|^6.2|^7.0"
+ },
+ "require-dev": {
+ "spatie/pixelmatch-php": "dev-main",
+ "spatie/ray": "^1.37"
+ },
+ "suggest": {
+ "spatie/pixelmatch-php": "Required to use the image snapshot assertions"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-v5": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Snapshots\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian De Deyne",
+ "email": "sebastian@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Snapshot testing with PHPUnit",
+ "homepage": "https://github.com/spatie/phpunit-snapshot-assertions",
+ "keywords": [
+ "assert",
+ "phpunit",
+ "phpunit-snapshot-assertions",
+ "snapshot",
+ "spatie",
+ "testing"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/phpunit-snapshot-assertions/issues",
+ "source": "https://github.com/spatie/phpunit-snapshot-assertions/tree/5.1.5"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ }
+ ],
+ "time": "2024-02-20T10:04:25+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v7.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
+ "reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^6.4|^7.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v7.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-04-01T11:04:53+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v7.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "408105dff4c104454100730bdfd1a9cdd993f04d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/408105dff4c104454100730bdfd1a9cdd993f04d",
+ "reference": "408105dff4c104454100730bdfd1a9cdd993f04d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v7.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-03-21T19:37:36+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
+ "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
+ "reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+ "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v7.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
+ "reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v7.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-02-22T20:27:20+00:00"
+ },
+ {
+ "name": "symfony/property-access",
+ "version": "v7.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/property-access.git",
+ "reference": "1c268ba954ccc5e78cf035b391abb67759e24423"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/property-access/zipball/1c268ba954ccc5e78cf035b391abb67759e24423",
+ "reference": "1c268ba954ccc5e78cf035b391abb67759e24423",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/property-info": "^6.4|^7.0"
+ },
+ "require-dev": {
+ "symfony/cache": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\PropertyAccess\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides functions to read and write from/to an object or array using a simple string notation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "access",
+ "array",
+ "extraction",
+ "index",
+ "injection",
+ "object",
+ "property",
+ "property-path",
+ "reflection"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/property-access/tree/v7.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-03-19T11:57:22+00:00"
+ },
+ {
+ "name": "symfony/property-info",
+ "version": "v7.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/property-info.git",
+ "reference": "b8844ddce7d53f78b57ec9be59da80fceddf3167"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/property-info/zipball/b8844ddce7d53f78b57ec9be59da80fceddf3167",
+ "reference": "b8844ddce7d53f78b57ec9be59da80fceddf3167",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/string": "^6.4|^7.0"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "<5.2",
+ "phpdocumentor/type-resolver": "<1.5.1",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/serializer": "<6.4"
+ },
+ "require-dev": {
+ "phpdocumentor/reflection-docblock": "^5.2",
+ "phpstan/phpdoc-parser": "^1.0",
+ "symfony/cache": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/serializer": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\PropertyInfo\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Kévin Dunglas",
+ "email": "dunglas@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Extracts information about PHP class' properties using metadata of popular sources",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "doctrine",
+ "phpdoc",
+ "property",
+ "symfony",
+ "type",
+ "validator"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/property-info/tree/v7.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-03-28T09:20:36+00:00"
+ },
+ {
+ "name": "symfony/serializer",
+ "version": "v7.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/serializer.git",
+ "reference": "dbdc0c04c28ac53de1fa35f92fca26e9b1345d98"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/dbdc0c04c28ac53de1fa35f92fca26e9b1345d98",
+ "reference": "dbdc0c04c28ac53de1fa35f92fca26e9b1345d98",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "<3.2.2",
+ "phpdocumentor/type-resolver": "<1.4.0",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/property-access": "<6.4",
+ "symfony/property-info": "<6.4",
+ "symfony/uid": "<6.4",
+ "symfony/validator": "<6.4",
+ "symfony/yaml": "<6.4"
+ },
+ "require-dev": {
+ "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0",
+ "seld/jsonlint": "^1.10",
+ "symfony/cache": "^6.4|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/console": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/filesystem": "^6.4|^7.0",
+ "symfony/form": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/mime": "^6.4|^7.0",
+ "symfony/property-access": "^6.4|^7.0",
+ "symfony/property-info": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3",
+ "symfony/uid": "^6.4|^7.0",
+ "symfony/validator": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0",
+ "symfony/var-exporter": "^6.4|^7.0",
+ "symfony/yaml": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Serializer\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/serializer/tree/v7.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-03-28T09:20:36+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "11bbf19a0fb7b36345861e85c5768844c552906e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e",
+ "reference": "11bbf19a0fb7b36345861e85c5768844c552906e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.4.2"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-19T21:51:00+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v7.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b",
+ "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v7.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-02-01T13:17:36+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v7.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "2d4fca631c00700597e9442a0b2451ce234513d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3",
+ "reference": "2d4fca631c00700597e9442a0b2451ce234513d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "symfony/console": "<6.4"
+ },
+ "require-dev": {
+ "symfony/console": "^6.4|^7.0"
+ },
+ "bin": [
+ "Resources/bin/yaml-lint"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Loads and dumps YAML files",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/yaml/tree/v7.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-01-23T15:02:46+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:36:25+00:00"
+ },
+ {
+ "name": "vlucas/phpdotenv",
+ "version": "v5.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vlucas/phpdotenv.git",
+ "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+ "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "graham-campbell/result-type": "^1.1.2",
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.2",
+ "symfony/polyfill-ctype": "^1.24",
+ "symfony/polyfill-mbstring": "^1.24",
+ "symfony/polyfill-php80": "^1.24"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-filter": "*",
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+ },
+ "suggest": {
+ "ext-filter": "Required to use the boolean validator."
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": true
+ },
+ "branch-alias": {
+ "dev-master": "5.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dotenv\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Vance Lucas",
+ "email": "vance@vancelucas.com",
+ "homepage": "https://github.com/vlucas"
+ }
+ ],
+ "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "support": {
+ "issues": "https://github.com/vlucas/phpdotenv/issues",
+ "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-11-12T22:43:29+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^8"
+ },
+ "platform-dev": [],
+ "platform-overrides": {
+ "php": "8.2"
+ },
+ "plugin-api-version": "2.6.0"
+}
diff --git a/_tests/custom_tests/phpunit.xml.dist b/_tests/custom_tests/phpunit.xml.dist
new file mode 100644
index 00000000..e9a0115d
--- /dev/null
+++ b/_tests/custom_tests/phpunit.xml.dist
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ tests
+
+
+
diff --git a/_tests/custom_tests/tests/BasicTest.php b/_tests/custom_tests/tests/BasicTest.php
new file mode 100644
index 00000000..e05704f5
--- /dev/null
+++ b/_tests/custom_tests/tests/BasicTest.php
@@ -0,0 +1,15 @@
+assertTrue( true );
+ }
+
+ public function test_run_e2e_exists() {
+ qit( [ 'run:e2e', '--help' ] );
+ // If we got here, it means the command ran successfully.
+ $this->assertTrue( true );
+ }
+}
\ No newline at end of file
diff --git a/_tests/custom_tests/tests/CompatibilityTest.php b/_tests/custom_tests/tests/CompatibilityTest.php
new file mode 100644
index 00000000..4a896fca
--- /dev/null
+++ b/_tests/custom_tests/tests/CompatibilityTest.php
@@ -0,0 +1,120 @@
+scaffold_test(),
+ '--plugin',
+ 'woocommerce:activate',
+ ]
+ );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_sut_and_bootstrap_additional() {
+ // Upload Woo Test Tag.
+ qit( [
+ 'tag:upload',
+ 'woocommerce:self-test-bootstrap-additional',
+ $this->scaffold_test(),
+ ]
+ );
+
+ // Run AutomateWoo, bootstrapping Woo.
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo',
+ $this->scaffold_test(),
+ '--plugin',
+ 'woocommerce:bootstrap:self-test-bootstrap-additional',
+ ]
+ );
+
+ // Cleanup.
+ qit( [ 'tag:delete', 'woocommerce:self-test-bootstrap-additional' ] );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_sut_and_test_additional() {
+ // Upload Woo Test Tag.
+ qit( [
+ 'tag:upload',
+ 'woocommerce:self-test-sut-and-test-additional',
+ $this->scaffold_test(),
+ ]
+ );
+
+ // Run AutomateWoo, bootstrapping Woo.
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo',
+ $this->scaffold_test(),
+ '--plugin',
+ 'woocommerce:test:self-test-sut-and-test-additional',
+ ]
+ );
+
+ // Cleanup.
+ qit( [ 'tag:delete', 'woocommerce:self-test-sut-and-test-additional' ] );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_multiple_tags_and_multiple_plugins_with_multiple_tags() {
+ qit( [
+ 'tag:upload',
+ 'automatewoo:self-test-multiple-test-tags',
+ $this->scaffold_test(),
+ ] );
+
+ qit( [
+ 'tag:upload',
+ 'automatewoo:self-test-multiple-test-tags-another',
+ $this->scaffold_test( 'another-tag' ),
+ ] );
+
+ qit( [
+ 'tag:upload',
+ 'woocommerce:self-test-multiple-test-tags',
+ $this->scaffold_test(),
+ ] );
+
+ qit( [
+ 'tag:upload',
+ 'woocommerce:self-test-multiple-test-tags-another',
+ $this->scaffold_test( 'another-tag' ),
+ ] );
+
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another',
+ '--plugin',
+ 'woocommerce:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another',
+ ] );
+
+ qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags' ] );
+ qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags-another' ] );
+ qit( [ 'tag:delete', 'woocommerce:self-test-multiple-test-tags' ] );
+ qit( [ 'tag:delete', 'woocommerce:self-test-multiple-test-tags-another' ] );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+}
\ No newline at end of file
diff --git a/_tests/custom_tests/tests/EnvTest.php b/_tests/custom_tests/tests/EnvTest.php
new file mode 100644
index 00000000..a75d8f74
--- /dev/null
+++ b/_tests/custom_tests/tests/EnvTest.php
@@ -0,0 +1,315 @@
+ "WordPress Version: 6.5.2-normalized"
+ $normalizedOutput = preg_replace( '/WordPress Version: .+/', 'WordPress Version: NORMALIZED', $normalizedOutput );
+
+ $this->assertMatchesNormalizedSnapshot( $normalizedOutput );
+ }
+
+ public function test_env_up_with_parameters() {
+ $output = qit( [
+ 'env:up',
+ '--wp',
+ '6.5',
+ '--php_version',
+ '8.3',
+ ]
+ );
+
+ // Check that WordPress Version is as expected:
+ $this->assertStringContainsString( 'WordPress Version: 6.5', $output );
+
+ // Check that PHP Version is as expected:
+ $this->assertStringContainsString( 'PHP Version: 8.3', $output );
+ }
+
+ public function test_env_up_with_object_cache() {
+ $output = qit( [
+ 'env:up',
+ '--object_cache',
+ ]
+ );
+
+ $this->assertStringContainsString( 'Redis Object Cache? Yes', $output );
+ }
+
+ public function test_env_up_with_file() {
+ $output = qit( [ 'env:up' ], [
+ 'wp' => '6.4',
+ 'php_version' => '8.2',
+ ] );
+
+ // Check that WordPress Version is as expected:
+ $this->assertStringContainsString( 'WordPress Version: 6.4', $output );
+
+ // Check that PHP Version is as expected:
+ $this->assertStringContainsString( 'PHP Version: 8.2', $output );
+ }
+
+ public function test_env_up_with_file_and_parameters() {
+ $output = qit( [ 'env:up' ], [
+ 'wp' => '6.4',
+ 'php_version' => '8.3',
+ ] );
+
+ // Check that WordPress Version is as expected:
+ $this->assertStringContainsString( 'WordPress Version: 6.4', $output );
+
+ // Check that PHP Version is as expected:
+ $this->assertStringContainsString( 'PHP Version: 8.3', $output );
+ }
+
+ public function test_env_up_with_plugins() {
+ $json = json_decode( qit( [ 'env:up', '--json' ], [
+ 'plugins' => [
+ 'automatewoo' => [
+ 'action' => 'activate',
+ ],
+ 'woocommerce' => [
+ 'action' => 'activate',
+ ],
+ ],
+ ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp plugin list',
+ ] );
+
+ /**
+ * name status update version update_version auto_update
+ * automatewoo active none 6.0.20 off
+ * woocommerce active available 8.7.0.20 8.8.2 off
+ * qit-wp-cli must-use none off
+ * wp-cli-github-cache must-use none off
+ */
+
+ // Iterate over each line, for the "automatewoo" and "woocommerce", normalize the version.
+ $lines = explode( "\n", $output );
+ $headers = preg_split( '/\s+/', trim( $lines[0] ) ); // Split the header to find the index of 'version'
+ $version_index = array_search( 'version', $headers ); // Locate the index of the 'version' column
+ $update_version_index = array_search( 'update_version', $headers ); // Locate the index of the 'update_version' column
+ $update_index = array_search( 'update', $headers ); // Locate the index of the 'update' column
+
+ foreach ( $lines as $key => $line ) {
+ if ( strpos( $line, 'automatewoo' ) !== false || strpos( $line, 'woocommerce' ) !== false ) {
+ $parts = preg_split( '/\s+/', trim( $line ) );
+ $parts[ $version_index ] = 'NORMALIZED_VERSION';
+ $parts[ $update_version_index ] = 'NORMALIZED_VERSION';
+ $parts[ $update_index ] = 'NORMALIZED';
+ $lines[ $key ] = implode( ' ', $parts );
+ }
+ }
+ $output = implode( "\n", $lines );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_with_additional_volumes() {
+ if ( file_exists( sys_get_temp_dir() . '/qit-tmp-plugin.php' ) && ! unlink( sys_get_temp_dir() . '/qit-tmp-plugin.php' ) ) {
+ throw new \RuntimeException( 'Could not delete the temporary file.' );
+ }
+
+ file_put_contents( sys_get_temp_dir() . '/qit-tmp-plugin.php',
+ <<<'PHP'
+assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_wordpress_stable_version() {
+ $json = json_decode( qit( [ 'env:up', '--json', '--wp', 'stable' ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp core check-update --force-check',
+ ] );
+
+ $this->assertStringContainsString( 'WordPress is at the latest version', $output );
+ }
+
+ public function test_env_up_wordpress_nightly_version() {
+ $json = json_decode( qit( [ 'env:up', '--json', '--wp', 'nightly' ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp core version',
+ ] );
+
+ // Preg match "6.6-alpha-58052"
+ $version_parts = explode( '-', $output );
+ $this->assertEquals( 3, count( $version_parts ) );
+ $this->assertEquals( 'alpha', $version_parts[1] );
+ $this->assertIsNumeric( $version_parts[2] );
+ }
+
+ public function test_env_up_woocommerce_stable_version() {
+ $json = json_decode( qit( [ 'env:up', '--json', '--woo', 'stable', ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp plugin update woocommerce',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_woocommerce_stable_version_alternative_syntax() {
+ $json = json_decode( qit( [ 'env:up', '--json', '--plugin', 'woocommerce', ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp plugin update woocommerce',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_woocommerce_nightly_version() {
+ $json = json_decode( qit( [
+ 'env:up',
+ '--json',
+ '--woo',
+ 'nightly',
+ ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp plugin get woocommerce',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_woocommerce_rc_version() {
+ $this->markTestSkipped();
+ $json = json_decode( qit( [
+ 'env:up',
+ '--json',
+ '--woo',
+ 'rc',
+ '--plugin',
+ 'https://github.com/woocommerce/woocommerce/releases/download/wc-beta-tester-2.3.0/woocommerce-beta-tester.zip:activate',
+ ] ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp plugin update woocommerce',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_with_additional_php_extensions() {
+ $json = json_decode( qit( [
+ 'env:up',
+ '--json',
+ '--php_extension',
+ 'gd',
+ ]
+ ), true );
+
+ $output = qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'php -m | grep gd',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_env_up_with_additional_themes() {
+ $json = json_decode( qit( [
+ 'env:up',
+ '--json',
+ '--theme',
+ 'storefront',
+ '--theme',
+ 'twentyseventeen',
+ ]
+ ), true );
+
+ $output = '';
+
+ $output .= qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp theme get storefront --fields=name,status',
+ ] );
+
+ $output .= "\n";
+
+ $output .= qit( [
+ 'env:exec',
+ '--env_id',
+ $json['env_id'],
+ 'wp theme get twentyseventeen --fields=name,status',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+}
\ No newline at end of file
diff --git a/_tests/custom_tests/tests/RunE2ETest.php b/_tests/custom_tests/tests/RunE2ETest.php
new file mode 100644
index 00000000..13f0c7fc
--- /dev/null
+++ b/_tests/custom_tests/tests/RunE2ETest.php
@@ -0,0 +1,207 @@
+scaffold_test(),
+ ],
+ [],
+ 1
+ );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_runs_scaffolded_e2e() {
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo',
+ $this->scaffold_test(),
+ '--plugin',
+ 'woocommerce:activate',
+ ]
+ );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_tag_and_run_test() {
+ qit( [
+ 'tag:upload',
+ 'automatewoo:self-test-tag-and-run',
+ $this->scaffold_test(),
+ ] );
+
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo:test:self-test-tag-and-run',
+ '--plugin',
+ 'woocommerce:activate',
+ ] );
+
+ qit( [ 'tag:delete', 'automatewoo:self-test-tag-and-run' ] );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_multiple_tags_and_run_tests() {
+ qit( [
+ 'tag:upload',
+ 'automatewoo:self-test-multiple-test-tags',
+ $this->scaffold_test(),
+ ] );
+
+ qit( [
+ 'tag:upload',
+ 'automatewoo:self-test-multiple-test-tags-another',
+ $this->scaffold_test( 'another-tag' ),
+ ] );
+
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo:test:self-test-multiple-test-tags,self-test-multiple-test-tags-another',
+ '--plugin',
+ 'woocommerce:activate',
+ ] );
+
+ qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags' ] );
+ qit( [ 'tag:delete', 'automatewoo:self-test-multiple-test-tags-another' ] );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_theme_as_sut() {
+ // Scaffold.
+ $scaffolded_dir = $this->scaffold_test();
+
+ $activate_theme_test = <<<'JS'
+import { test, expect } from '@playwright/test';
+import qit from '/qitHelpers';
+
+test('I can activate Deli', async ({ page }) => {
+ await qit.loginAsAdmin(page);
+ await page.getByRole('link', { name: 'Appearance' }).click();
+ await expect(page.getByRole('cell', { name: 'Deli' })).toBeVisible();
+ await page.getByRole('link', { name: 'Install Parent Theme' }).click();
+ await page.getByRole('link', { name: 'Activate “Storefront”' }).click();
+ await page.getByLabel('Activate Deli').click();
+ await page.goto('/');
+});
+JS;
+
+ // Create a new test that will activate the theme.
+ if ( ! file_put_contents( $scaffolded_dir . '/activate-theme.spec.js', $activate_theme_test ) ) {
+ throw new \RuntimeException( 'Failed to create the scaffolded test file.' );
+
+ }
+
+ // Run.
+ $output = qit( [
+ 'run:e2e',
+ 'deli',
+ $scaffolded_dir,
+ '--testing_theme',
+ ] );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ $this->assertMatchesNormalizedSnapshot( $output );
+ }
+
+ public function test_run_with_snapshot() {
+ // Scaffold.
+ $scaffolded_dir = $this->scaffold_test();
+
+ $activate_theme_test = <<<'JS'
+import { test, expect } from '@playwright/test';
+import qit from '/qitHelpers';
+
+test('I can activate Deli', async ({ page }) => {
+ await qit.loginAsAdmin(page);
+ await page.getByRole('link', { name: 'Appearance' }).click();
+ await expect(page.getByRole('cell', { name: 'Deli' })).toBeVisible();
+ await page.getByRole('link', { name: 'Install Parent Theme' }).click();
+ await page.getByRole('link', { name: 'Activate “Storefront”' }).click();
+ await page.getByLabel('Activate Deli').click();
+ await page.goto('/');
+ await expect(page).toHaveScreenshot('home.png', { maxDiffPixels: 100 });
+});
+JS;
+
+ // Create a new test that will activate the theme.
+ if ( ! file_put_contents( $scaffolded_dir . '/activate-theme.spec.js', $activate_theme_test ) ) {
+ throw new \RuntimeException( 'Failed to create the scaffolded test file.' );
+ }
+
+ $this->assertFileDoesNotExist( $scaffolded_dir . '/__snapshots__' );
+
+ // Run the first time to generate snapshots.
+ $output = qit( [
+ 'run:e2e',
+ 'deli',
+ $scaffolded_dir,
+ '--testing_theme',
+ '--update_snapshots',
+ ] );
+
+ $this->assertFileExists( $scaffolded_dir . '/__snapshots__' );
+ $this->assertMatchesNormalizedSnapshot( $this->normalize_scaffolded_test_run_output( $output ) );
+
+ // Run the second time to validate snapshot.
+ $output = qit( [
+ 'run:e2e',
+ 'deli',
+ $scaffolded_dir,
+ '--testing_theme',
+ ] );
+
+ $this->assertMatchesNormalizedSnapshot( $this->normalize_scaffolded_test_run_output( $output ) );
+ }
+
+ public function test_playwright_config_override() {
+ $output = qit( [
+ 'run:e2e',
+ 'automatewoo',
+ $this->scaffold_test(),
+ '--plugin',
+ 'woocommerce:activate',
+ ], [
+ 'playwright_config' => [
+ 'reportSlowTests' => [
+ 'max' => 10,
+ 'threshold' => 1,
+ ],
+ ],
+ ]
+ );
+
+ $output = $this->normalize_scaffolded_test_run_output( $output );
+
+ // "Loading environment config from override parameter /tmp/qit-env-97d237784cddc7ec1341113ca364110d.json..." Normalize "97d237784cddc7ec1341113ca364110d".
+ $output = preg_replace( '/qit-env-[a-f0-9]{32}/', 'qit-env-', $output );
+
+ // "Slow test file: [automatewoo-local] › automatewoo/local/example.spec.js (7.1s)" Normalize "7.1s".
+ $output = preg_replace( '/\d+\.\d+s/', '