diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml deleted file mode 100644 index b8335f17b..000000000 --- a/.github/workflows/deploy-playground-and-test.yml +++ /dev/null @@ -1,283 +0,0 @@ -name: Deploy Playground Apps to Vercel and Test on React -env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} - -on: - pull_request: - push: - branches: - - develop - -jobs: - deploy: - runs-on: ubuntu-24.04 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - path: | - ~/.npm - ./node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: Install Dependencies - run: | - npm i - npm install lerna - npm install vercel@33.2.0 - npm list - - - name: Check ESLint Errors - run: npm run lint - - - name: Check Prettier Formatting - run: npm run prettier:check - - - name: Build SDKs - run: | - npm run build - npm run build:bundler:local - - - name: Normalize branch name - run: | - echo "BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-')" >> "$GITHUB_ENV" - shell: bash - - - name: Deploy react playground to Vercel - run: | - npx vercel link --yes --project react-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.react.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_REACT_PLAYGROUND }} - - - name: Deploy connect next playground to Vercel - run: | - npx vercel link --yes --project connect-next-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull --environment=preview -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.connect-next.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_CONNECT_NEXT_PLAYGROUND }} - - - name: Deploy web-js playground to Vercel - run: | - npx vercel link --yes --project web-js-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_PLAYGROUND }} - - - name: Deploy web-js-script playground to Vercel - run: | - npx vercel link --yes --project web-js-script-playground --scope corbado -t $VERCEL_TOKEN - npx vercel pull -t $VERCEL_TOKEN - npx vercel build -t $VERCEL_TOKEN - url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" - npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js-script.playground.corbado.io - env: - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_SCRIPT_PLAYGROUND }} - - test: - needs: deploy - timeout-minutes: 60 - runs-on: ubuntu-24.04 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - - - name: Get installed Playwright version - id: playwright-version - run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" - - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - path: | - ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: List the state of node modules - continue-on-error: true - run: | - cd packages/tests-e2e - npm list - - - name: Install Dependencies - run: | - cd packages/tests-e2e - npm install - sudo apt-get update - sudo apt-get install -y libxml2-utils - - - uses: actions/cache@v3 - id: playwright-cache - with: - path: '~/.cache/ms-playwright' - key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' - restore-keys: | - ${{ runner.os }}-playwright- - - - name: Install Playwright's dependencies - if: steps.playwright-cache.outputs.cache-hit != 'true' - run: | - cd packages/tests-e2e - npx playwright install --with-deps - - - name: Create a log stream on AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - run: | - TIMESTAMP=$(date +%s000) - echo "LOG_STREAM_NAME=javascript-$TIMESTAMP" >> $GITHUB_ENV - aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "javascript-$TIMESTAMP" - - - name: Run Complete tests for react - run: | - cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.react.playground.corbado.io" - set +e - npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV - fi - env: - PLAYWRIGHT_NUM_CORES: 4 - PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} - PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} - PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ github.ref_name }} - continue-on-error: true - - - uses: actions/upload-artifact@v4 - with: - name: playwright-report-complete-react - path: packages/tests-e2e/playwright-report/ - retention-days: 30 - - - name: Send logs to AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - run: | - cd packages/tests-e2e - TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) - FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) - SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) - ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) - TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) - PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) - FAILED=$((FAILURES + ERRORS)) - TIMESTAMP=$(date +%s000) - LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"complete\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" - aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - - name: Run Connect tests for react - run: | - cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" - set +e - npx playwright test --config=playwright.config.connect.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV - fi - env: - PLAYWRIGHT_NUM_CORES: 4 - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} - PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} - PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} - continue-on-error: true - - - uses: actions/upload-artifact@v4 - with: - name: playwright-report-connect-react - path: packages/tests-e2e/playwright-report/ - retention-days: 30 - - - name: Send logs to AWS CloudWatch - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} - run: | - cd packages/tests-e2e - TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) - FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) - SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) - ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) - TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) - PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) - FAILED=$((FAILURES + ERRORS)) - TIMESTAMP=$(date +%s000) - LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"connect\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" - aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" - - - name: Aggregate results - run: | - FAILED_STEPS="" - if [ "${COMPLETE_REACT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-React" - fi - if [ "${COMPLETE_WEBJS_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-WebJs" - fi - if [ "${COMPLETE_WEBJSSCRIPT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Complete-WebJsScript" - fi - if [ "${CONNECT_REACT_FAILED}" = "true" ]; then - FAILED_STEPS="${FAILED_STEPS} Connect-React" - fi - - if [ -n "$FAILED_STEPS" ]; then - echo "The following test steps have failed: $FAILED_STEPS" - exit 1 - else - echo "All tests passed." - fi diff --git a/.github/workflows/deploy-playgrounds.yml b/.github/workflows/deploy-playgrounds.yml new file mode 100644 index 000000000..0b4d240fa --- /dev/null +++ b/.github/workflows/deploy-playgrounds.yml @@ -0,0 +1,102 @@ +name: Deploy Playground Apps to Vercel +env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} + +on: + pull_request: + push: + branches: + - develop + +jobs: + deploy: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: | + ~/.npm + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install Dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Check ESLint Errors + run: npm run lint + + - name: Check Prettier Formatting + run: npm run prettier:check + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + + - name: Normalize branch name + run: | + echo "BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-')" >> "$GITHUB_ENV" + shell: bash + + - name: Deploy react playground to Vercel + run: | + npx vercel link --yes --project react-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.react.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_REACT_PLAYGROUND }} + + - name: Deploy connect next playground to Vercel + run: | + npx vercel link --yes --project connect-next-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull --environment=preview -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.connect-next.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_CONNECT_NEXT_PLAYGROUND }} + + - name: Deploy web-js playground to Vercel + run: | + npx vercel link --yes --project web-js-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_PLAYGROUND }} + + - name: Deploy web-js-script playground to Vercel + run: | + npx vercel link --yes --project web-js-script-playground --scope corbado -t $VERCEL_TOKEN + npx vercel pull -t $VERCEL_TOKEN + npx vercel build -t $VERCEL_TOKEN + url="$(npx vercel deploy --prebuilt -t ${{ secrets.VERCEL_TOKEN }})" + npx vercel alias -S corbado -t ${{ secrets.VERCEL_TOKEN }} "$url" $BRANCH_NAME.web-js-script.playground.corbado.io + env: + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_WEB_JS_SCRIPT_PLAYGROUND }} + diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index eb4ea9ea0..3fb601c85 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -20,10 +20,6 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 - - name: Get installed Playwright version - id: playwright-version - run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" - - name: Cache node modules id: cache-npm uses: actions/cache@v3 @@ -32,12 +28,30 @@ jobs: with: path: | ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install root dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + + - name: Get installed Playwright version + id: playwright-version + run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} name: List the state of node modules continue-on-error: true @@ -45,7 +59,7 @@ jobs: cd packages/tests-e2e npm list - - name: Install Dependencies + - name: Install dependencies run: | cd packages/tests-e2e npm install @@ -79,16 +93,10 @@ jobs: - name: Run Complete tests for react run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.react.playground.corbado.io" - set +e npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_REACT_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} @@ -125,16 +133,10 @@ jobs: - name: Run Complete tests for web-js run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.web-js.playground.corbado.io" - set +e npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_WEBJS_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} @@ -171,16 +173,10 @@ jobs: - name: Run Complete tests for web-js-script run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.web-js-script.playground.corbado.io" - set +e npx playwright test --config=playwright.config.complete.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "COMPLETE_WEBJSSCRIPT_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} @@ -217,22 +213,22 @@ jobs: - name: Run Connect tests for react run: | cd packages/tests-e2e - BRANCH_NAME=$(echo $BRANCH_NAME_RAW | tr '/_' '-') - export PLAYWRIGHT_TEST_URL="https://$BRANCH_NAME.connect-next.playground.corbado.io" - set +e npx playwright test --config=playwright.config.connect.ts - EXIT_CODE=$? - if [ $EXIT_CODE -ne 0 ]; then - echo "CONNECT_REACT_FAILED=true" >> $GITHUB_ENV - fi env: PLAYWRIGHT_NUM_CORES: 4 - BACKEND_API_BASIC_AUTH: ${{ secrets.BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} + NEXT_PUBLIC_CORBADO_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_CORBADO_PROJECT_ID }} + NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX: ${{ secrets.NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX }} + CORBADO_BACKEND_API_URL: ${{ secrets.CORBADO_BACKEND_API_URL }} + CORBADO_BACKEND_API_BASIC_AUTH: ${{ secrets.CORBADO_BACKEND_API_BASIC_AUTH }} PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} - GITHUB_RUN_ID: ${{ github.run_id }} - SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} - GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} + AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} + AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} + AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_CONNECT_PLAYGROUND }} continue-on-error: true - uses: actions/upload-artifact@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..805aab6cb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,157 @@ +name: Test on React +env: + BRANCH_NAME_RAW: ${{ github.head_ref || github.ref_name }} + +on: + pull_request: + push: + branches: + - develop + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - testType: complete + configFile: playwright.config.complete.ts + - testType: connect + configFile: playwright.config.connect.ts + env: + PLAYWRIGHT_NUM_CORES: 4 + PLAYWRIGHT_TEST_URL: ${{ secrets.PLAYWRIGHT_TEST_URL }} + + PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} + + NEXT_PUBLIC_CORBADO_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_CORBADO_PROJECT_ID }} + NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX: ${{ secrets.NEXT_PUBLIC_CORBADO_FRONTEND_API_URL_SUFFIX }} + CORBADO_BACKEND_API_URL: ${{ secrets.CORBADO_BACKEND_API_URL }} + CORBADO_BACKEND_API_BASIC_AUTH: ${{ secrets.CORBADO_BACKEND_API_BASIC_AUTH }} + PLAYWRIGHT_CONNECT_PROJECT_ID: ${{ secrets.PLAYWRIGHT_CONNECT_PROJECT_ID }} + PLAYWRIGHT_NGROK_AUTH_TOKEN: ${{ secrets.PLAYWRIGHT_NGROK_AUTH_TOKEN }} + AWS_COGNITO_USER_POOL_ID: ${{ secrets.AWS_COGNITO_USER_POOL_ID }} + AWS_COGNITO_CLIENT_ID: ${{ secrets.AWS_COGNITO_CLIENT_ID }} + AWS_COGNITO_CLIENT_SECRET: ${{ secrets.AWS_COGNITO_CLIENT_SECRET }} + AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_CONNECT_PLAYGROUND }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_CONNECT_PLAYGROUND }} + + GITHUB_RUN_ID: ${{ github.run_id }} + SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} + GITHUB_BRANCH_NAME: ${{ github.ref_name }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: | + ~/.npm + ./node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}-force-1 + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: Install root dependencies + run: | + npm i + npm install lerna + npm install vercel@33.2.0 + npm list + + - name: Build SDKs + run: | + npm run build + npm run build:bundler:local + + - name: Get installed Playwright version + id: playwright-version + run: echo "::set-output name=version::$(yarn why --json @playwright/test | grep -h 'workspace:.' | jq --raw-output '.children[].locator' | sed -e 's/@playwright\/test@.*://')" + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: List the state of node modules + continue-on-error: true + run: | + cd packages/tests-e2e + npm list + + - name: Install dependencies + run: | + cd packages/tests-e2e + npm install + sudo apt-get update + sudo apt-get install -y libxml2-utils + + - uses: actions/cache@v3 + id: playwright-cache + with: + path: '~/.cache/ms-playwright' + key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' + restore-keys: | + ${{ runner.os }}-playwright- + + - name: Install Playwright's dependencies + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: | + cd packages/tests-e2e + npx playwright install --with-deps + + - name: Create a log stream on AWS CloudWatch + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + TIMESTAMP=$(date +%s000) + RANDOM_SUFFIX=$(head /dev/urandom | tr -dc a-z0-9 | head -c 6) + LOG_STREAM_NAME="javascript-${TIMESTAMP}-${RANDOM_SUFFIX}" + echo "LOG_STREAM_NAME=$LOG_STREAM_NAME" >> $GITHUB_ENV + aws logs create-log-stream --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" + + - name: Run ${{ matrix.testType }} tests for react + working-directory: packages/tests-e2e + run: | + npx playwright test --config=${{ matrix.configFile }} + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-${{ matrix.testType }}-react + path: packages/tests-e2e/playwright-report/ + retention-days: 30 + + - name: Send logs to AWS CloudWatch + if: always() + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd packages/tests-e2e + TESTS=$(xmllint --xpath 'string(/testsuites/@tests)' test-results/results.xml) + FAILURES=$(xmllint --xpath 'string(/testsuites/@failures)' test-results/results.xml) + SKIPPED=$(xmllint --xpath 'string(/testsuites/@skipped)' test-results/results.xml) + ERRORS=$(xmllint --xpath 'string(/testsuites/@errors)' test-results/results.xml) + TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) + PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) + FAILED=$((FAILURES + ERRORS)) + TIMESTAMP=$(date +%s000) + LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"{\\\"application\\\":\\\"${{ matrix.testType }}\\\",\\\"platform\\\":\\\"react\\\",\\\"run_type\\\":\\\"commitly\\\",\\\"execution_time\\\":$TIME,\\\"passed\\\":$PASSED,\\\"failed\\\":$FAILED,\\\"link\\\":\\\"https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}\\\"}\"}]" + aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" diff --git a/package-lock.json b/package-lock.json index 51de54f94..3c29bf140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8271,6 +8271,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, "node_modules/@zkochan/js-yaml": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", @@ -8301,6 +8308,20 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -8452,6 +8473,16 @@ "dev": true, "license": "MIT" }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -8533,6 +8564,27 @@ "dev": true, "license": "ISC" }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/arg": { "version": "4.1.3", "dev": true, @@ -8985,6 +9037,153 @@ "version": "2.11.0", "license": "MIT" }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -9110,6 +9309,16 @@ "node": ">=12.17" } }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -9412,6 +9621,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -9524,6 +9749,19 @@ "node": ">=6" } }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -9566,6 +9804,48 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/cliui": { "version": "8.0.1", "dev": true, @@ -9751,6 +10031,62 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -9790,6 +10126,16 @@ "dev": true, "license": "ISC" }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/conventional-changelog-angular": { "version": "7.0.0", "dev": true, @@ -10360,6 +10706,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -13280,6 +13636,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "dev": true, @@ -16003,6 +16372,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "dev": true, @@ -16408,6 +16787,13 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/path-key": { "version": "3.1.1", "dev": true, @@ -16441,6 +16827,13 @@ "dev": true, "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "dev": true, @@ -17111,6 +17504,42 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -17636,6 +18065,30 @@ "node": ">=4" } }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", @@ -18110,6 +18563,115 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", + "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.6", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/serve/node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/serve/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -19793,6 +20355,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "dev": true, @@ -19885,6 +20458,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", @@ -21746,6 +22329,34 @@ "node": ">=10" } }, + "node_modules/wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-port/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", @@ -21781,6 +22392,10 @@ "resolved": "playground/web-js", "link": true }, + "node_modules/web-js-script": { + "resolved": "playground/web-js-script", + "link": true + }, "node_modules/web-streams-polyfill": { "version": "3.3.2", "dev": true, @@ -22033,6 +22648,76 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wildcard": { "version": "2.0.1", "dev": true, @@ -22460,7 +23145,7 @@ }, "packages/shared-util": { "name": "@corbado/shared-util", - "version": "1.0.10", + "version": "1.0.11", "license": "MIT", "devDependencies": { "@babel/core": "^7.24.0", @@ -22919,9 +23604,11 @@ "devDependencies": { "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", + "get-port": "^5.1.1", "ngrok": "^5.0.0-beta.2", "node-2fa": "^2.0.3", - "playwright-slack-report": "^1.1.72" + "playwright-slack-report": "^1.1.72", + "wait-port": "^1.1.0" } }, "packages/types": { @@ -24058,7 +24745,15 @@ }, "devDependencies": { "dotenv-webpack": "^8.0.1", - "html-webpack-plugin": "^5.6.3" + "html-webpack-plugin": "^5.6.3", + "serve": "^14.2.4" + } + }, + "playground/web-js-script": { + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "serve": "^14.2.4" } } } diff --git a/packages/tests-e2e/.env.complete.ci b/packages/tests-e2e/.env.complete.ci index 636344bea..5e5a4cb60 100644 --- a/packages/tests-e2e/.env.complete.ci +++ b/packages/tests-e2e/.env.complete.ci @@ -1,3 +1,3 @@ DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/packages/tests-e2e/.env.complete.example b/packages/tests-e2e/.env.complete.example index 7b0d36135..2a5850d4e 100644 --- a/packages/tests-e2e/.env.complete.example +++ b/packages/tests-e2e/.env.complete.example @@ -1,7 +1,7 @@ # save to .env.local -PLAYWRIGHT_TEST_URL=http://localhost:3000 +PLAYWRIGHT_TEST_URL=http://localhost DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io FRONTEND_API_URL_SUFFIX= PLAYWRIGHT_GOOGLE_EMAIL= PLAYWRIGHT_GOOGLE_PASSWORD= diff --git a/packages/tests-e2e/.env.connect.ci b/packages/tests-e2e/.env.connect.ci index 136997e7b..6aff2d306 100644 --- a/packages/tests-e2e/.env.connect.ci +++ b/packages/tests-e2e/.env.connect.ci @@ -1,3 +1,3 @@ -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io #DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io #FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io diff --git a/packages/tests-e2e/.env.connect.example b/packages/tests-e2e/.env.connect.example index 7d08eec39..6d922847d 100644 --- a/packages/tests-e2e/.env.connect.example +++ b/packages/tests-e2e/.env.connect.example @@ -1,6 +1,6 @@ # save to .env.local -PLAYWRIGHT_TEST_URL=https://develop.connect-next.playground.corbado.io +PLAYWRIGHT_TEST_URL=http://localhost DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io -BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io -BACKEND_API_BASIC_AUTH= +CORBADO_BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io +CORBADO_BACKEND_API_BASIC_AUTH= PLAYWRIGHT_NGROK_AUTH_TOKEN= diff --git a/packages/tests-e2e/README.md b/packages/tests-e2e/README.md index 78fbe14ee..d8359a789 100644 --- a/packages/tests-e2e/README.md +++ b/packages/tests-e2e/README.md @@ -4,46 +4,24 @@ This package currently contains all the end-to-end tests for testing the React p ## Running Tests Locally -This package is intended to test the local Playground React deployment connected to the staging backend deployment. +Playground is locally run within Playwright for both Complete and Connect tests. For reference look at `utils/playground.ts`. -### Setup +This means that tests can simply be run with a single command. The command depends on whether you want to run it headless or with UI. -Copy the contents of .env.example into .env.local. -For `PLAYWRIGHT_JWT_TOKEN` environment variable, request the JWT token from Martin or Stefan. -For `PLAYWRIGHT_MICROSOFT_EMAIL` and `PLAYWRIGHT_MICROSOFT_PASSWORD` environment variables, request the credentials from Anders or Martin. You can also create your own Microsoft account for testing. - -Make sure that the local Playground React deployment is configured to connect to the staging frontend API endpoints in `playground/react/.env`. +### Headless ``` -REACT_APP_CORBADO_FRONTEND_API_URL_SUFFIX=frontendapi.cloud.corbado-staging.io +cd packages/tests-e2e +npm run e2e:complete +npm run e2e:connect ``` -Run the Playground React project locally. - -```console -$ cd playground/react -$ npm start -``` - -Now Playwright is ready to test the local Playground deployment. - ### With UI -```console -$ cd packages/tests-e2e -$ npx playwright test --config=playwright.config.complete.ts --ui --project=nightly ``` - -### From CLI - -```console -npx playwright test --config=playwright.config.complete.ts --project=nightly -``` - -Alternatively, you can do: - -```console -npm run e2e:ui:nightly +cd packages/tests-e2e +npm run e2e:complete:ui +npm run e2e:connect:ui ``` ## Generating JWT Token diff --git a/packages/tests-e2e/package.json b/packages/tests-e2e/package.json index aa87efa6b..c869b6e0f 100644 --- a/packages/tests-e2e/package.json +++ b/packages/tests-e2e/package.json @@ -24,9 +24,11 @@ "devDependencies": { "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", + "get-port": "^5.1.1", "ngrok": "^5.0.0-beta.2", "node-2fa": "^2.0.3", - "playwright-slack-report": "^1.1.72" + "playwright-slack-report": "^1.1.72", + "wait-port": "^1.1.0" }, "dependencies": { "dotenv": "^16.4.5" diff --git a/packages/tests-e2e/playwright.config.complete.ts b/packages/tests-e2e/playwright.config.complete.ts index 5f3163afa..8364eaaf6 100644 --- a/packages/tests-e2e/playwright.config.complete.ts +++ b/packages/tests-e2e/playwright.config.complete.ts @@ -13,6 +13,27 @@ if (process.env.CI) { dotenv.config({ path: path.resolve(__dirname, '.env.complete.local'), override: true }); } +// type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; +// +// const PLAYGROUND_TYPE: PlaygroundType = +// (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; +// +// let webServerCommand: string; +// +// switch (PLAYGROUND_TYPE) { +// case 'react': +// webServerCommand = 'cd ../../playground/react && npm i && npm run build && npm run preview'; +// break; +// case 'web-js': +// webServerCommand = 'npm run serve:web-js-playground'; +// break; +// case 'web-js-script': +// webServerCommand = 'npm run serve:web-js-script-playground'; +// break; +// default: +// throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); +// } + export default defineConfig({ testDir: './src/complete', // fullyParallel: true, @@ -53,7 +74,7 @@ export default defineConfig({ use: { actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none - baseURL: process.env.PLAYWRIGHT_TEST_URL, + // baseURL: process.env.PLAYWRIGHT_TEST_URL, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'retain-on-failure', @@ -72,4 +93,5 @@ export default defineConfig({ testMatch: ['scenarios/passkey-list-general/*.ts'], }, ], + globalSetup: 'src/complete/utils/playground.ts', }); diff --git a/packages/tests-e2e/playwright.config.connect.ts b/packages/tests-e2e/playwright.config.connect.ts index 526da0fb3..fd2662c30 100644 --- a/packages/tests-e2e/playwright.config.connect.ts +++ b/packages/tests-e2e/playwright.config.connect.ts @@ -45,10 +45,10 @@ export default defineConfig({ }, use: { userAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15.3.2) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/129.0.6668.29 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15.3.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', actionTimeout: operationTimeout, // default: none navigationTimeout: operationTimeout, // default: none - baseURL: process.env.PLAYWRIGHT_TEST_URL, + // baseURL: process.env.PLAYWRIGHT_TEST_URL, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'retain-on-failure', @@ -71,4 +71,5 @@ export default defineConfig({ testMatch: ['scenarios/misc.spec.ts'], }, ], + globalSetup: 'src/connect/utils/Playground.ts', }); diff --git a/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts b/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts index b9a854142..d5e4a6a15 100644 --- a/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts +++ b/packages/tests-e2e/src/complete/models/CorbadoAuthModel.ts @@ -34,10 +34,10 @@ export class CorbadoAuthModel { this.phoneVerify = new PhoneVerifyBlockModel(page); } - async load(projectId: string, passkeySupport?: boolean, hashCode?: string) { + async load(projectId: string, port: number, passkeySupport?: boolean, hashCode?: string) { this.projectId = projectId; - let url = `/${this.projectId}/auth`; + let url = `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/${this.projectId}/auth`; if (hashCode) { url += `#${hashCode}`; } diff --git a/packages/tests-e2e/src/complete/models/PasskeyListModel.ts b/packages/tests-e2e/src/complete/models/PasskeyListModel.ts index ac14fc440..3fc560b36 100644 --- a/packages/tests-e2e/src/complete/models/PasskeyListModel.ts +++ b/packages/tests-e2e/src/complete/models/PasskeyListModel.ts @@ -21,14 +21,14 @@ export class PasskeyListModel { this.virtualAuthenticator = virtualAuthenticator; } - async load(projectId: string, passkeySupport?: boolean) { + async load(projectId: string, port: number, passkeySupport?: boolean) { this.projectId = projectId; if (passkeySupport !== undefined) { await this.virtualAuthenticator.addWebAuthn(passkeySupport); } - const url = `/${this.projectId}/auth#signup-init`; + const url = `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/${this.projectId}/auth#signup-init`; await this.page.goto(url); await this.page.waitForSelector('.cb-container-body'); diff --git a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts index acb6916cb..17291b31d 100644 --- a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts +++ b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/EmailVerifyBlockModel.ts @@ -27,8 +27,8 @@ export class EmailVerifyBlockModel { } } - async clickEmailLink(projectID: string, email: string, authType: AuthType, type: LinkType) { - const link = await this.#generateEmailLink(projectID, email, authType, type); + async clickEmailLink(projectID: string, port: number, email: string, authType: AuthType, type: LinkType) { + const link = await this.#generateEmailLink(projectID, port, email, authType, type); await this.page.goto(link); } @@ -64,7 +64,7 @@ export class EmailVerifyBlockModel { ).toBeVisible(); } - async #generateEmailLink(projectID: string, email: string, authType: AuthType, linkType: LinkType) { + async #generateEmailLink(projectID: string, port: number, email: string, authType: AuthType, linkType: LinkType) { const key = `cbo_auth_process-${projectID}`; const cboAuthProcessRaw = await this.page.evaluate(k => localStorage.getItem(k), key); if (!cboAuthProcessRaw) { @@ -89,6 +89,6 @@ export class EmailVerifyBlockModel { const serializedBlock = btoa(JSON.stringify(urlBlock)); - return `${process.env.PLAYWRIGHT_TEST_URL}/${projectID}/auth?corbadoEmailLinkID=${serializedBlock}&corbadoToken=${linkType}`; + return `http://localhost:${port.toString()}/${projectID}/auth?corbadoEmailLinkID=${serializedBlock}&corbadoToken=${linkType}`; } } diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts index fe964f40a..f1ad5c1d7 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-username-verification-at-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: email (verification at signup) + username', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -21,14 +26,18 @@ test.describe('tests that focus on these identifiers: email (verification at sig ]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); const username = SignupInitBlockModel.generateRandomUsername(); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts index eec905dd0..21c52c5d4 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-login.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: email (verification at login)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -20,14 +25,18 @@ test.describe('tests that focus on these identifiers: email (verification at log IdentifierVerification.EmailOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -44,7 +53,7 @@ test.describe('tests that focus on these identifiers: email (verification at log }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -58,7 +67,7 @@ test.describe('tests that focus on these identifiers: email (verification at log }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -70,7 +79,7 @@ test.describe('tests that focus on these identifiers: email (verification at log await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); @@ -82,7 +91,7 @@ test.describe('tests that focus on these identifiers: email (verification at log // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -93,7 +102,7 @@ test.describe('tests that focus on these identifiers: email (verification at log await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts index 0c2978d98..8f6a1b88c 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-at-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,10 +10,13 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; // this is the default component config that we offer test.describe('tests that focus on these identifiers: email (verification at signup)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -21,14 +26,18 @@ test.describe('tests that focus on these identifiers: email (verification at sig IdentifierVerification.EmailOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -45,7 +54,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -61,7 +70,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -73,7 +82,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); @@ -85,7 +94,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -96,7 +105,7 @@ test.describe('tests that focus on these identifiers: email (verification at sig await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts index a69569d4b..0622178ba 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/email-verification-none.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: email (verification none)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -18,14 +23,18 @@ test.describe('tests that focus on these identifiers: email (verification none)' await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -41,7 +50,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -55,7 +64,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -67,7 +76,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); @@ -79,7 +88,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -90,7 +99,7 @@ test.describe('tests that focus on these identifiers: email (verification none)' await model.logout(); // no passkey is available => we expect an emailVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); await model.emailVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts index 8dbb7e022..3465c7421 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/phone-verification-at-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/PhoneVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: phone (verification at signup)', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -20,14 +25,18 @@ test.describe('tests that focus on these identifiers: phone (verification at sig IdentifierVerification.PhoneOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -44,7 +53,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -60,7 +69,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig }); test('signup with explicit fallback', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -72,7 +81,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig await model.logout(); // no passkey is available => we expect an phoneVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillPhone(identifier); await model.loginInit.submitPrimary(); await model.phoneVerify.fillOtpCode(OtpCodeType.Correct); @@ -84,7 +93,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig // during signup, the fallback is initiated automatically (user does not have to click on the fallback button) // during login, we don't ask the user to append a passkey test('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomPhone(); await model.signupInit.fillPhone(identifier); @@ -95,7 +104,7 @@ test.describe('tests that focus on these identifiers: phone (verification at sig await model.logout(); // no passkey is available => we expect an phoneVerify block - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); await model.loginInit.fillPhone(identifier); await model.loginInit.submitPrimary(); await model.phoneVerify.fillOtpCode(OtpCodeType.Correct); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts index b45301f80..9bb45fce9 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-component-configs/username.spec.ts @@ -1,10 +1,15 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; import { IdentifierEnforceVerification, IdentifierType, ScreenNames } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('tests that focus on these identifiers: username', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -12,14 +17,18 @@ test.describe('tests that focus on these identifiers: username', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with passkey (happy path)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomUsername(); await model.signupInit.fillUsername(identifier); @@ -35,7 +44,7 @@ test.describe('tests that focus on these identifiers: username', () => { }); test('signup with passkey (one passkey retry)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomUsername(); await model.signupInit.fillUsername(identifier); @@ -51,7 +60,7 @@ test.describe('tests that focus on these identifiers: username', () => { // this tests a dead end scenario where passkeys are not available but the user can not signup // TODO: fix test.skip('signup with fallback (passkeys not available)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const identifier = SignupInitBlockModel.generateRandomUsername(); await model.signupInit.fillUsername(identifier); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts index abd51326b..1ca5dc63e 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-link-verify-obfuscation.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../../fixtures/CorbadoAuth'; import { LinkType } from '../../models/corbado-auth-blocks/EmailVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -9,9 +11,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('email-verify block should obfuscate email addresses if they have not been provided by the user during login', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -22,14 +27,18 @@ test.describe('email-verify block should obfuscate email addresses if they have ]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('email is obfuscated during login if the login is started with username', async ({ model, page }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); const username = SignupInitBlockModel.generateRandomUsername(); @@ -38,18 +47,18 @@ test.describe('email-verify block should obfuscate email addresses if they have await model.signupInit.submitPrimary(); await model.expectScreen(ScreenNames.EmailLinkSentSignup); - await model.emailVerify.clickEmailLink(projectId, email, AuthType.Login, LinkType.Correct); + await model.emailVerify.clickEmailLink(projectId, port, email, AuthType.Login, LinkType.Correct); await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(username); await model.loginInit.submitPrimary(); await model.expectScreen(ScreenNames.EmailLinkSentLogin); await expect(page.getByText(email)).toHaveCount(0); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(email); await model.loginInit.submitPrimary(); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts index 1d5f8ce6e..c06064719 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/email-otp-verify-general.spec.ts @@ -8,10 +8,14 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; +import type { ChildProcess } from 'node:child_process'; // Here we test everything on LoginInit screen test.describe('general email-verify functionalities', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -19,14 +23,18 @@ test.describe('general email-verify functionalities', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('signup with fallback (one retry)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); @@ -41,7 +49,7 @@ test.describe('general email-verify functionalities', () => { }); test('signup with fallback + identifier change', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email1 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email1); @@ -58,7 +66,7 @@ test.describe('general email-verify functionalities', () => { await model.logout(); // only login with email2 should be possible - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(email1); await model.loginInit.submitPrimary(); await model.loginInit.expectTextError("Couldn't find your account."); @@ -69,7 +77,7 @@ test.describe('general email-verify functionalities', () => { }); test('signup with fallback + identifier change (abort + continue with email 1)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email1 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email1); @@ -86,7 +94,7 @@ test.describe('general email-verify functionalities', () => { await model.logout(); // only login with email1 should be possible - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(email2); await model.loginInit.submitPrimary(); await model.loginInit.expectTextError("Couldn't find your account."); @@ -100,7 +108,7 @@ test.describe('general email-verify functionalities', () => { // then we start a new process and signup with email 2 // during verification we try to switch back to email 1 => this must fail test('signup with fallback + identifier change (email already exists)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email1 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email1); @@ -109,7 +117,7 @@ test.describe('general email-verify functionalities', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email2 = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email2); @@ -123,7 +131,7 @@ test.describe('general email-verify functionalities', () => { // TODO: fix test.skip('signup with explicit fallback (too many wrong codes)', async ({ model }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); await model.signupInit.fillEmail(email); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts index 5a74ba044..932b848cb 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-email.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { IdentifierEnforceVerification, @@ -6,10 +8,13 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; // Here we test everything on LoginInit screen test.describe('login-init', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -17,14 +22,18 @@ test.describe('login-init', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('bad user input: empty email', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.loginInit.fillEmailUsername(''); await model.loginInit.submitPrimary(); @@ -33,7 +42,7 @@ test.describe('login-init', () => { // TODO: fix and enable test.skip('bad user input: invalid email', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.loginInit.fillEmailUsername('a@a'); await model.loginInit.submitPrimary(); @@ -41,7 +50,7 @@ test.describe('login-init', () => { }); test('bad user input: non-existing email', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.loginInit.fillEmailUsername('integration-test+notexist@corbado.com'); await model.loginInit.submitPrimary(); @@ -53,7 +62,7 @@ test.describe('login-init', () => { // TODO: bad user input: non-existing phone test('switch to signup', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.expectScreen(ScreenNames.InitLogin); await model.loginInit.navigateToSignup(); @@ -63,7 +72,7 @@ test.describe('login-init', () => { // after a signup with passkey a passkey button should be shown // the button should disappear after when one explicitly switches the identifier test('passkey button should be shown', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.defaultSignupWithPasskey(); @@ -71,7 +80,7 @@ test.describe('login-init', () => { await model.expectScreen(ScreenNames.InitLogin); await model.loginInit.expectPasskeyButton(true); - await model.load(projectId); + await model.load(projectId, port); await model.loginInit.expectPasskeyButton(true); await model.passkeyVerify.performAutomaticPasskeyVerification(() => model.loginInit.submitPasskeyButton()); @@ -86,10 +95,10 @@ test.describe('login-init', () => { // after a signup without passkey no passkey button should be shown test('passkey button should not be shown', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.defaultSignupWithFallback(); - await model.load(projectId, undefined, 'login-init'); + await model.load(projectId, port, undefined, 'login-init'); // after a signup with passkey a passkey button should be shown => we use it to login await model.expectScreen(ScreenNames.InitLogin); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts index 284c4f43d..18532b3a9 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/login-init-no-public-signup.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../../fixtures/CorbadoAuth'; import { IdentifierEnforceVerification, @@ -6,11 +8,14 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; // Developers can disable public signup // In that case UI components no longer will allow a signup test.describe('login-init no public signup', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -26,19 +31,25 @@ test.describe('login-init no public signup', () => { false, false, ); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); - test.afterAll(async () => deleteProjectNew(projectId)); + test.afterAll(async () => { + await deleteProjectNew(projectId); + + killPlaygroundNew(server); + }); test('switch to signup should not be possible (button)', async ({ model, page }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.expectScreen(ScreenNames.InitLogin); await expect(page.getByRole('button', { name: 'Sign up' })).toBeHidden(); }); test('switch to signup should not be possible (hashCode)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.expectScreen(ScreenNames.InitLogin); }); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts index 0344f3da0..15fa69962 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/phone-otp-verify-obfuscation.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../../fixtures/CorbadoAuth'; import { OtpCodeType } from '../../models/corbado-auth-blocks/PhoneVerifyBlockModel'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; @@ -8,9 +10,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('phone-verify block should obfuscate phone numbers if they have not been provided by the user during login', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -19,14 +24,18 @@ test.describe('phone-verify block should obfuscate phone numbers if they have no makeIdentifier(IdentifierType.Phone, IdentifierEnforceVerification.None, true, [IdentifierVerification.PhoneOtp]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('phone number is obfuscated during login if the login is started with username', async ({ model, page }) => { - await model.load(projectId, false, 'signup-init'); + await model.load(projectId, port, false, 'signup-init'); const phone = SignupInitBlockModel.generateRandomPhone(); const username = SignupInitBlockModel.generateRandomUsername(); @@ -37,14 +46,14 @@ test.describe('phone-verify block should obfuscate phone numbers if they have no await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.fillEmailUsername(username); await model.loginInit.submitPrimary(); await model.expectScreen(ScreenNames.PhoneOtpLogin); await expect(page.getByText(phone)).toHaveCount(0); - await model.load(projectId, false, 'login-init'); + await model.load(projectId, port, false, 'login-init'); await model.loginInit.switchToInputPhone(); await model.loginInit.fillPhone(phone); await model.loginInit.submitPrimary(); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts index 597caf893..77f6f2f65 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/routing.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; import { @@ -7,9 +9,12 @@ import { ScreenNames, } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('routing', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -17,26 +22,30 @@ test.describe('routing', () => { await setComponentConfig(projectId, [ makeIdentifier(IdentifierType.Email, IdentifierEnforceVerification.None, true, [IdentifierVerification.EmailOtp]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('initial routing should happen depending on the hashCode (signup-init)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.expectScreen(ScreenNames.InitSignup); }); test('initial routing should happen depending on the hashCode (login-init)', async ({ model }) => { - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.expectScreen(ScreenNames.InitLogin); }); test('initial routing should happen depending on local storage', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); // by default the signup screen is loaded await model.expectScreen(ScreenNames.InitSignup); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts index a78ec0896..659c4db49 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/signup-init.spec.ts @@ -1,10 +1,15 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { SignupInitBlockModel } from '../../models/corbado-auth-blocks/SignupInitBlockModel'; import { IdentifierEnforceVerification, IdentifierType, IdentifierVerification } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('signup-init', () => { let projectId: string; + let server: ChildProcess; + let port: number; test.beforeAll(async () => { projectId = await createProjectNew(); @@ -14,14 +19,18 @@ test.describe('signup-init', () => { makeIdentifier(IdentifierType.Phone, IdentifierEnforceVerification.None, true, [IdentifierVerification.PhoneOtp]), makeIdentifier(IdentifierType.Username, IdentifierEnforceVerification.None, true, []), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('bad user input: all fields empty', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.fillEmail(''); await model.signupInit.fillPhone(''); @@ -34,7 +43,7 @@ test.describe('signup-init', () => { }); test('bad user input: invalid fields (simple)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.fillEmail('asdf@asdf'); await model.signupInit.fillPhone('1234'); @@ -47,7 +56,7 @@ test.describe('signup-init', () => { }); test('bad user input: duplicate fields', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = SignupInitBlockModel.generateRandomEmail(); const phone = SignupInitBlockModel.generateRandomPhone(); @@ -60,7 +69,7 @@ test.describe('signup-init', () => { await model.passkeyAppend.startPasskeyOperation(true); await model.logout(); - await model.load(projectId, undefined, 'signup-init'); + await model.load(projectId, port, undefined, 'signup-init'); await model.signupInit.fillEmail(email); await model.signupInit.fillPhone(phone); diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts index 402ab93bd..f1ce8784d 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts @@ -1,3 +1,5 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../../fixtures/CorbadoAuth'; import { IdentifierEnforceVerification, @@ -15,9 +17,12 @@ import { makeSocialProvider, setComponentConfig, } from '../../utils/developerpanel'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; test.describe('social logins', () => { let projectId: string; + let server: ChildProcess; + let port: number; // Google social login requires longer timeout test.describe.configure({ timeout: socialTotalTimeout }); @@ -42,14 +47,18 @@ test.describe('social logins', () => { makeSocialProvider(SocialProviderType.Google), ], ); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); test.afterEach(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); test('socials should be rendered on UI component if they are activated', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); // by default the signup screen is loaded await model.expectScreen(ScreenNames.InitSignup); @@ -70,7 +79,7 @@ test.describe('social logins', () => { // Reason for skip: https://www.notion.so/Issues-related-to-email-and-social-login-in-tests-javascript-complete-1bceeb954aa280148c34d10aac8c2117 test.skip('signup with socials should be possible (account does not exist)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -81,7 +90,7 @@ test.describe('social logins', () => { }); test.skip('signup with social should be possible (account exists, social has been linked)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -93,7 +102,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.resubmitSocialGoogle(); // TODO: should successfully log in, but gets redirected to login-init instead. @@ -104,7 +113,7 @@ test.describe('social logins', () => { test.skip('signup with social should not be possible (account exists, social has not been linked)', async ({ model, }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -116,7 +125,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); await model.signupInit.submitSocialGoogle(email, password, secret); await model.expectScreen(ScreenNames.InitLogin); @@ -124,7 +133,7 @@ test.describe('social logins', () => { test.skip('login with social should be possible (account does not exist)', async ({ model }) => { // redirects to passkey append screen - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -135,7 +144,7 @@ test.describe('social logins', () => { }); test.skip('login with social should be possible (account exists, social has been linked)', async ({ model }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -147,7 +156,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.signupInit.resubmitSocialGoogle(); await model.expectScreen(ScreenNames.End); @@ -157,7 +166,7 @@ test.describe('social logins', () => { test.skip('login with social should not be possible (account exists, social has not been linked)', async ({ model, }) => { - await model.load(projectId, true, 'signup-init'); + await model.load(projectId, port, true, 'signup-init'); const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; @@ -169,7 +178,7 @@ test.describe('social logins', () => { await model.expectScreen(ScreenNames.End); await model.logout(); - await model.load(projectId, true, 'login-init'); + await model.load(projectId, port, true, 'login-init'); await model.signupInit.submitSocialGoogle(email, password, secret); // TODO: should redirect to login-init screen, but gets successfully logged in insteaad. diff --git a/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts b/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts index e1ebff189..55f67ec69 100644 --- a/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/passkey-list-general/general.spec.ts @@ -1,9 +1,14 @@ +import type { ChildProcess } from 'node:child_process'; + +import { passkeyListTest } from '../../fixtures/PasskeyList'; import { IdentifierEnforceVerification, IdentifierType, IdentifierVerification } from '../../utils/constants'; import { createProjectNew, deleteProjectNew, makeIdentifier, setComponentConfig } from '../../utils/developerpanel'; -import { passkeyListTest } from '../../fixtures/PasskeyList'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../../utils/playground'; passkeyListTest.describe('passkey list - general', () => { let projectId: string; + let server: ChildProcess; + let port: number; passkeyListTest.beforeAll(async () => { projectId = await createProjectNew(); @@ -13,14 +18,18 @@ passkeyListTest.describe('passkey list - general', () => { IdentifierVerification.EmailOtp, ]), ]); + + ({ server, port } = await spawnPlaygroundNew(projectId)); }); passkeyListTest.afterAll(async () => { await deleteProjectNew(projectId); + + killPlaygroundNew(server); }); passkeyListTest('passkey list allows adding and deleting passkeys (passkeys are supported)', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.expectPasskeys(0); await model.appendNewPasskey(true); @@ -31,7 +40,7 @@ passkeyListTest.describe('passkey list - general', () => { // currently it seems impossible to test for duplicate passkeys with virtual authenticator passkeyListTest('passkey list error handling (cancel, duplicate passkeys)', async ({ model }) => { - await model.load(projectId, true); + await model.load(projectId, port, true); await model.expectPasskeys(0); await model.appendNewPasskey(false); diff --git a/packages/tests-e2e/src/complete/utils/developerpanel.ts b/packages/tests-e2e/src/complete/utils/developerpanel.ts index 36713e016..32673dce9 100644 --- a/packages/tests-e2e/src/complete/utils/developerpanel.ts +++ b/packages/tests-e2e/src/complete/utils/developerpanel.ts @@ -34,7 +34,7 @@ export const createProjectNew = async () => { body: JSON.stringify({ name, allowStaticChallenges: true, - webauthnRPID: process.env.CI ? 'playground.corbado.io' : 'localhost', + webauthnRPID: process.env.CI ? 'localhost' : 'localhost', }), }); expect(createRes.ok).toBeTruthy(); diff --git a/packages/tests-e2e/src/complete/utils/playground.ts b/packages/tests-e2e/src/complete/utils/playground.ts new file mode 100644 index 000000000..a2423b914 --- /dev/null +++ b/packages/tests-e2e/src/complete/utils/playground.ts @@ -0,0 +1,121 @@ +import type { ChildProcess } from 'node:child_process'; +import { spawn } from 'node:child_process'; + +import getPort from 'get-port'; +import path from 'path'; +import waitPort from 'wait-port'; + +type PlaygroundType = 'react' | 'web-js' | 'web-js-script'; +const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'react'; + +function getPlaygroundDir(): string { + switch (PLAYGROUND_TYPE) { + case 'react': + return path.resolve(__dirname, '../../../../../playground/react'); + case 'web-js': + return path.resolve(__dirname, '../../../../../playground/web-js'); + case 'web-js-script': + return path.resolve(__dirname, '../../../../../playground/web-js-script'); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundBuildArgs(): string[] | null { + switch (PLAYGROUND_TYPE) { + case 'react': + return ['run', 'build']; + case 'web-js': + return ['run', 'build']; + case 'web-js-script': + return null; + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundStartArgs(port: number): string[] { + switch (PLAYGROUND_TYPE) { + case 'react': + return ['run', 'preview', '--', '--port', port.toString()]; + case 'web-js': + return ['run', 'serve', '--', '-l', port.toString()]; + case 'web-js-script': + return ['run', 'serve', '--', '-l', port.toString()]; + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +export async function spawnPlaygroundNew(projectId: string): Promise<{ + server: ChildProcess; + port: number; +}> { + const port = await getPort(); + + const playgroundDir = getPlaygroundDir(); + const server = spawn('npm', getPlaygroundStartArgs(port), { + cwd: playgroundDir, + env: { + ...process.env, + VITE_CORBADO_PROJECT_ID_ManualTesting: projectId, + }, + stdio: 'ignore', + shell: true, + }); + const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); + if (!ok) { + server.kill(); + throw new Error(`Server never came up on port ${port}`); + } + + return { server, port }; +} + +export function killPlaygroundNew(server: ChildProcess) { + server.kill(); +} + +export default async function installPlaygroundDeps() { + const playgroundDir = getPlaygroundDir(); + + const installProcess = spawn('npm', ['install'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + + await new Promise((resolve, reject) => { + installProcess.on('close', (code: number) => { + if (code === 0) { + console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); + const buildCommand = getPlaygroundBuildArgs(); + if (!buildCommand) { + console.log(`[Global Setup] No build step required for ${PLAYGROUND_TYPE}.`); + return resolve(); + } + const buildProcess = spawn('npm', buildCommand, { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + buildProcess.on('close', (buildCode: number) => { + if (buildCode === 0) { + console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); + resolve(); + } else { + reject(new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`)); + } + }); + buildProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start build process: ${err.message}`)); + }); + } else { + reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); + } + }); + installProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start npm install process: ${err.message}`)); + }); + }); +} diff --git a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts index 7d1ad37e6..5f427b53f 100644 --- a/packages/tests-e2e/src/connect/fixtures/BaseTest.ts +++ b/packages/tests-e2e/src/connect/fixtures/BaseTest.ts @@ -5,7 +5,9 @@ import { CDPSessionManager } from '../utils/CDPSessionManager'; import { NetworkRequestBlocker } from '../utils/NetworkRequestBlocker'; import { VirtualAuthenticator } from '../utils/VirtualAuthenticator'; -export const test = base.extend<{ model: BaseModel }>({ +export const test = base.extend<{ + model: BaseModel; +}>({ model: async ({ page }, use) => { const cdpManager = new CDPSessionManager(); await cdpManager.initialize(page); diff --git a/packages/tests-e2e/src/connect/models/BaseModel.ts b/packages/tests-e2e/src/connect/models/BaseModel.ts index f3d87f0ad..db6920381 100644 --- a/packages/tests-e2e/src/connect/models/BaseModel.ts +++ b/packages/tests-e2e/src/connect/models/BaseModel.ts @@ -42,16 +42,16 @@ export class BaseModel { this.storage = new StorageModel(page); } - loadSignup() { - return this.page.goto('/signup'); + loadSignup(port: number) { + return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/signup`); } - loadLogin() { - return this.page.goto('/login'); + loadLogin(port: number) { + return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login`); } - loadHome() { - return this.page.goto('/home'); + loadHome(port: number) { + return this.page.goto(`${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/home`); } expectScreen(screenName: ScreenNames) { diff --git a/packages/tests-e2e/src/connect/models/StorageModel.ts b/packages/tests-e2e/src/connect/models/StorageModel.ts index f28ba1971..b8ad9c458 100644 --- a/packages/tests-e2e/src/connect/models/StorageModel.ts +++ b/packages/tests-e2e/src/connect/models/StorageModel.ts @@ -11,8 +11,10 @@ export class StorageModel { this.page = page; } - async loadInvitationToken() { - await this.page.goto('/login?invitationToken=inv-token-correct'); + async loadInvitationToken(port: number) { + await this.page.goto( + `${process.env.PLAYWRIGHT_TEST_URL}:${port.toString()}/login?invitationToken=inv-token-correct`, + ); await expectScreen(this.page, ScreenNames.InitLogin); } diff --git a/packages/tests-e2e/src/connect/models/WebhookModel.ts b/packages/tests-e2e/src/connect/models/WebhookModel.ts index 51dd6d274..8c99f12a9 100644 --- a/packages/tests-e2e/src/connect/models/WebhookModel.ts +++ b/packages/tests-e2e/src/connect/models/WebhookModel.ts @@ -46,7 +46,7 @@ export class WebhookModel { } }); this.webhookServer.listen(port, () => { - console.log(`Webhook server running at http://localhost:${port}`); + console.log(`Webhook server running at ${process.env.PLAYWRIGHT_TEST_URL}:${port}`); resolve(this.webhookServer); }); }); @@ -55,10 +55,10 @@ export class WebhookModel { addr: port, authtoken: process.env.PLAYWRIGHT_NGROK_AUTH_TOKEN, }); - const createRes = await fetch(`${process.env.BACKEND_API_URL}/v2/webhookEndpoints`, { + const createRes = await fetch(`${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints`, { method: 'POST', headers: { - Authorization: `Basic ${process.env.BACKEND_API_BASIC_AUTH}`, + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ @@ -87,13 +87,16 @@ export class WebhookModel { throw new Error('PLAYWRIGHT_CONNECT_PROJECT_ID not set'); } - const deleteRes = await fetch(`${process.env.BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, { - method: 'DELETE', - headers: { - Authorization: `Basic ${process.env.BACKEND_API_BASIC_AUTH}`, - 'Content-Type': 'application/json', + const deleteRes = await fetch( + `${process.env.CORBADO_BACKEND_API_URL}/v2/webhookEndpoints/${this.webhookEndpointID}`, + { + method: 'DELETE', + headers: { + Authorization: `Basic ${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`, + 'Content-Type': 'application/json', + }, }, - }); + ); expect(deleteRes.ok).toBeTruthy(); this.webhookServer.close(); diff --git a/packages/tests-e2e/src/connect/scenarios/append.spec.ts b/packages/tests-e2e/src/connect/scenarios/append.spec.ts index d50d4138e..2ac8d5c37 100644 --- a/packages/tests-e2e/src/connect/scenarios/append.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/append.spec.ts @@ -1,13 +1,27 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect } from '@playwright/test'; import { test } from '../fixtures/BaseTest'; import { password, ScreenNames } from '../utils/Constants'; import { loadBeforePasskeyAppend, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; test.describe('append component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadBeforePasskeyAppend(test); test('successful passkey append on login', async ({ model }) => { @@ -31,9 +45,20 @@ test.describe('append component', () => { }); test.describe('skip append component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); test('Corbado FAPI unavailable', async ({ model }) => { await model.home.logout(); diff --git a/packages/tests-e2e/src/connect/scenarios/hooks.ts b/packages/tests-e2e/src/connect/scenarios/hooks.ts index 4f05a47e2..b8ba59780 100644 --- a/packages/tests-e2e/src/connect/scenarios/hooks.ts +++ b/packages/tests-e2e/src/connect/scenarios/hooks.ts @@ -57,9 +57,10 @@ export function loadInvitationToken( PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions >, + getPort: () => number, ) { test.beforeEach(async ({ model }) => { - await model.storage.loadInvitationToken(); + await model.storage.loadInvitationToken(getPort()); }); } @@ -68,14 +69,15 @@ export function setupUser( PlaywrightTestArgs & PlaywrightTestOptions & { model: BaseModel }, PlaywrightWorkerArgs & PlaywrightWorkerOptions >, + getPort: () => number, invited = true, append = true, ) { test.beforeEach(async ({ model }) => { if (invited) { - await model.storage.loadInvitationToken(); + await model.storage.loadInvitationToken(getPort()); } - await model.loadSignup(); + await model.loadSignup(getPort()); await model.expectScreen(ScreenNames.InitSignup); await model.createUser(invited, append); await model.expectScreen(ScreenNames.Home); diff --git a/packages/tests-e2e/src/connect/scenarios/login.spec.ts b/packages/tests-e2e/src/connect/scenarios/login.spec.ts index 104413850..17833f71d 100644 --- a/packages/tests-e2e/src/connect/scenarios/login.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/login.spec.ts @@ -1,11 +1,25 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect } from '@playwright/test'; import { test } from '../fixtures/BaseTest'; import { ErrorTexts, password, ScreenNames } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadInvitationToken, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('login component (without invitation token)', () => { - setupUser(test, false); + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + + setupUser(test, () => port, false); test('successful login with credentials', async ({ model }) => { await model.home.logout(); @@ -21,8 +35,19 @@ test.describe('login component (without invitation token)', () => { }); test.describe('login component (with invitation token, without passkeys)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); test('successful login with credentials', async ({ model }) => { await model.home.logout(); @@ -43,9 +68,20 @@ test.describe('login component (with invitation token, without passkeys)', () => }); test.describe('login component (with invitation token, with passkeys)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, true); + setupUser(test, () => port, true, true); test('successful login with passkey', async ({ model }) => { await model.home.logout(); @@ -116,7 +152,7 @@ test.describe('login component (with invitation token, with passkeys)', () => { await model.passkeyList.deletePasskey(0); await model.passkeyList.expectPasskeys(0); - await model.loadHome(); + await model.loadHome(port); await model.expectScreen(ScreenNames.Home); await model.login.submitConditionalUI(async () => { @@ -141,12 +177,23 @@ test.describe('login component (with invitation token, with passkeys)', () => { }); test.describe('login component (without user)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - loadInvitationToken(test); + loadInvitationToken(test, () => port); test('attempt login with incomplete credentials', async ({ model }) => { - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLogin); await model.login.submitEmail('', false); @@ -154,7 +201,7 @@ test.describe('login component (without user)', () => { }); test('attempt login with unknown credentials', async ({ model }) => { - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLogin); await model.login.submitEmail('integration-test+unknown@corbado.com', false); @@ -164,7 +211,7 @@ test.describe('login component (without user)', () => { test('Corbado FAPI unavailable', async ({ model }) => { await model.blocker.blockCorbadoFAPI(); - await model.loadLogin(); + await model.loadLogin(port); // It seems that the InitLogin page is now cached so that email needs to be submitted before reaching the InitLoginFallback screen. await model.login.submitEmail('integration-test+dummy@corbado.com', false); await model.expectScreen(ScreenNames.InitLoginFallback); @@ -175,7 +222,7 @@ test.describe('login component (without user)', () => { await model.storage.checkInvitationToken(); const processId = await model.storage.getProcessID(); - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLogin); await model.storage.checkInvitationToken(); await model.storage.checkProcessID(processId); @@ -187,7 +234,7 @@ test.describe('login component (without user)', () => { await model.storage.setLoginLifetime(Math.floor(Date.now() / 1000) - 1); await model.storage.deleteInvitationToken(); - await model.loadLogin(); + await model.loadLogin(port); await model.expectScreen(ScreenNames.InitLoginFallback); }); }); diff --git a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts index 001cff981..4a736d9cf 100644 --- a/packages/tests-e2e/src/connect/scenarios/misc.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/misc.spec.ts @@ -1,5 +1,8 @@ +import type { ChildProcess } from 'node:child_process'; + import { test } from '../fixtures/BaseTest'; import { ScreenNames, WebhookTypes } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadBeforePasskeyAppend, loadPasskeyList, @@ -11,9 +14,20 @@ import { test.describe.serial('webhook tests', () => { test.describe('login component (webhook)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, true); + setupUser(test, () => port, true, true); setupWebhooks(test, [WebhookTypes.Login]); test('successful login with passkey (+ webhook)', async ({ model }) => { @@ -31,9 +45,20 @@ test.describe.serial('webhook tests', () => { }); test.describe('append component (webhook)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadBeforePasskeyAppend(test); setupWebhooks(test, [WebhookTypes.Create]); @@ -49,9 +74,20 @@ test.describe.serial('webhook tests', () => { }); test.describe('passkey-list component (webhook)', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadPasskeyList(test); setupWebhooks(test, [WebhookTypes.Create, WebhookTypes.Delete]); diff --git a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts index 880d0b8a2..5d39061c8 100644 --- a/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts +++ b/packages/tests-e2e/src/connect/scenarios/passkey-list.spec.ts @@ -1,11 +1,25 @@ +import type { ChildProcess } from 'node:child_process'; + import { expect, test } from '../fixtures/BaseTest'; import { ErrorTexts, ScreenNames } from '../utils/Constants'; +import { killPlaygroundNew, spawnPlaygroundNew } from '../utils/Playground'; import { loadPasskeyList, setupNetworkBlocker, setupUser, setupVirtualAuthenticator } from './hooks'; test.describe('passkey-list component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, false); + setupUser(test, () => port, true, false); loadPasskeyList(test); test('list, delete, create passkey', async ({ model }) => { @@ -25,7 +39,7 @@ test.describe('passkey-list component', () => { test('Connect Token endpoint unavailable during passkey creation', async ({ model }) => { await model.passkeyList.expectPasskeys(0); - await model.blocker.blockCorbadoConnectTokenEndpoint(); + await model.blocker.blockCorbadoConnectTokenEndpoint(port); await model.page.getByRole('button', { name: 'Add a passkey' }).click(); await model.expectError(ErrorTexts.PasskeyCreateFail); @@ -61,7 +75,7 @@ test.describe('passkey-list component', () => { await model.passkeyList.createPasskey(true); await model.passkeyList.expectPasskeys(1); - await model.blocker.blockCorbadoConnectTokenEndpoint(); + await model.blocker.blockCorbadoConnectTokenEndpoint(port); await model.passkeyList.deletePasskey(0); await model.expectError(ErrorTexts.PasskeyDeleteFail); @@ -82,12 +96,23 @@ test.describe('passkey-list component', () => { }); test.describe('skip passkey-list component', () => { + let server: ChildProcess; + let port: number; + + test.beforeAll(async () => { + ({ server, port } = await spawnPlaygroundNew()); + }); + + test.afterAll(() => { + killPlaygroundNew(server); + }); + setupVirtualAuthenticator(test); setupNetworkBlocker(test); - setupUser(test, true, true); + setupUser(test, () => port, true, true); test('Connect Token endpoint unavailable', async ({ model }) => { - await model.blocker.blockCorbadoConnectTokenEndpoint(); + await model.blocker.blockCorbadoConnectTokenEndpoint(port); await model.home.gotoPasskeyList(); await model.expectScreen(ScreenNames.PasskeyList); @@ -106,7 +131,7 @@ test.describe('skip passkey-list component', () => { await model.home.gotoPasskeyList(); await model.expectScreen(ScreenNames.PasskeyList); await model.passkeyList.expectPasskeys(1); - await model.loadHome(); + await model.loadHome(port); await model.expectScreen(ScreenNames.Home); expect(await model.storage.getManageLifetime()).toBeGreaterThan(Math.floor(Date.now() / 1000)); diff --git a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts index c780a351f..612c405b1 100644 --- a/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts +++ b/packages/tests-e2e/src/connect/utils/NetworkRequestBlocker.ts @@ -25,10 +25,10 @@ export class NetworkRequestBlocker { }); } - blockCorbadoConnectTokenEndpoint() { + blockCorbadoConnectTokenEndpoint(port: number) { // This is sufficient, as the connectTokens endpoint is called from /passkey-list handler return this.#cdpClient.send('Network.setBlockedURLs', { - urls: ['*.playground.corbado.io/passkey-list'], + urls: [`localhost:${port.toString()}/passkey-list`], }); } } diff --git a/packages/tests-e2e/src/connect/utils/Playground.ts b/packages/tests-e2e/src/connect/utils/Playground.ts new file mode 100644 index 000000000..a2d526421 --- /dev/null +++ b/packages/tests-e2e/src/connect/utils/Playground.ts @@ -0,0 +1,100 @@ +import type { ChildProcess } from 'node:child_process'; +import { spawn } from 'node:child_process'; + +import getPort from 'get-port'; +import path from 'path'; +import waitPort from 'wait-port'; + +type PlaygroundType = 'connect-next' | 'connect-web-js'; +const PLAYGROUND_TYPE: PlaygroundType = (process.env.PLAYGROUND_TYPE as PlaygroundType) || 'connect-next'; + +export type PlaygroundInfo = { + server: ChildProcess; + port: number; +}; + +function getPlaygroundDir(): string { + switch (PLAYGROUND_TYPE) { + case 'connect-next': + return path.resolve(__dirname, '../../../../../playground/connect-next'); + case 'connect-web-js': + return path.resolve(__dirname, '../../../../../playground/connect-web-js'); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +function getPlaygroundArgs(port: number): string[] { + switch (PLAYGROUND_TYPE) { + case 'connect-next': + return ['run', 'start', '--', '--port', port.toString()]; + case 'connect-web-js': + throw new Error(`Unimplemented: ${PLAYGROUND_TYPE}`); + default: + throw new Error(`Unknown PLAYGROUND_TYPE: ${PLAYGROUND_TYPE}`); + } +} + +export async function spawnPlaygroundNew(): Promise { + const port = await getPort(); + + const playgroundDir = getPlaygroundDir(); + const server = spawn('npm', getPlaygroundArgs(port), { + cwd: playgroundDir, + env: { + ...process.env, + }, + stdio: 'ignore', + shell: true, + }); + const ok = await waitPort({ host: 'localhost', port, timeout: 15_000, output: 'silent' }); + if (!ok) { + server.kill(); + throw new Error(`Server never came up on port ${port}`); + } + + return { server, port }; +} + +export function killPlaygroundNew(server: ChildProcess) { + server.kill(); +} + +export default async function installPlaygroundDeps() { + const playgroundDir = getPlaygroundDir(); + + const installProcess = spawn('npm', ['install'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + + await new Promise((resolve, reject) => { + installProcess.on('close', (code: number) => { + if (code === 0) { + console.log(`[Global Setup] Dependencies installed successfully in ${playgroundDir}.`); + const buildProcess = spawn('npm', ['run', 'build'], { + cwd: playgroundDir, + stdio: 'inherit', + shell: true, + }); + buildProcess.on('close', (buildCode: number) => { + if (buildCode === 0) { + console.log(`[Global Setup] Playground built successfully in ${playgroundDir}.`); + resolve(); + } else { + reject(new Error(`[Global Setup] npm run build failed in ${playgroundDir} with code ${buildCode}`)); + } + }); + buildProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start build process: ${err.message}`)); + }); + } else { + reject(new Error(`[Global Setup] npm install failed in ${playgroundDir} with code ${code}`)); + } + }); + installProcess.on('error', (err: Error) => { + reject(new Error(`[Global Setup] Failed to start npm install process: ${err.message}`)); + }); + }); +} diff --git a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts index dea81b8a4..16c6a8b7d 100644 --- a/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts +++ b/packages/tests-e2e/src/connect/utils/VirtualAuthenticator.ts @@ -84,7 +84,7 @@ export class VirtualAuthenticator { credential: { credentialId: '', // 'WZuSfPDeCfXUMqO3vcVZ6ZYY0w2W4NpLcLzTjMl4qns=', isResidentCredential: true, - rpId: 'connect-next.playground.corbado.io', + rpId: 'localhost', privateKey: 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgz/eSahk8R0fk3Jjpcbd1LPc2gGKyzEG23UFIbFTqSbyhRANCAAQ4a8dJ559cf0cZcg0U7k5oCofmtOzuqXDSwzP8LLhv0InronrySiaWAGuWFpVsbNyOnWSd6VZJU8wiFKSMiDWN', userHandle: '', // 'TDBlaFVpNnRNQg==', diff --git a/playground/connect-next/app/login/actions.ts b/playground/connect-next/app/login/actions.ts index 9ccb62476..8acfee5b1 100644 --- a/playground/connect-next/app/login/actions.ts +++ b/playground/connect-next/app/login/actions.ts @@ -50,6 +50,13 @@ export async function postPasskeyLoginNew(signedPasskeyData: string, clientState signedPasskeyData: signedPasskeyData, }); + console.log('url: ', url); + console.log('auth: ', `${process.env.CORBADO_BACKEND_API_BASIC_AUTH}`); + + console.log('Calling postPasskeyLoginNew with:'); + console.log('signedPasskeyData:', signedPasskeyData); + console.log('clientState:', clientState); + const response = await fetch(url, { method: 'POST', headers: { @@ -60,7 +67,16 @@ export async function postPasskeyLoginNew(signedPasskeyData: string, clientState body: body, }); - const out = await response.json(); + const clonedResponse = response.clone(); + let out: any; + try { + out = await response.json(); + } catch (err) { + const text = await clonedResponse.text(); // Get raw HTML or empty string + console.error('Failed to parse JSON. Raw response:', text.slice(0, 300)); + throw new Error('Invalid JSON response from backend'); + } + console.log(out); await postPasskeyLogin(out.session); diff --git a/playground/web-js-script/package.json b/playground/web-js-script/package.json new file mode 100644 index 000000000..9a5bce3b8 --- /dev/null +++ b/playground/web-js-script/package.json @@ -0,0 +1,16 @@ +{ + "name": "web-js-script", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "serve": "npx serve --single" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "serve": "^14.2.4" + } +} diff --git a/playground/web-js/package.json b/playground/web-js/package.json index 2984265d2..0b0c42464 100644 --- a/playground/web-js/package.json +++ b/playground/web-js/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "start": "cross-env NODE_ENV=local webpack-dev-server --open --mode development", - "build": "cross-env NODE_ENV=vercel webpack --mode production" + "build": "cross-env NODE_ENV=vercel webpack --mode production", + "serve": "npx serve dist --single" }, "keywords": [], "author": "", @@ -15,6 +16,7 @@ }, "devDependencies": { "dotenv-webpack": "^8.0.1", - "html-webpack-plugin": "^5.6.3" + "html-webpack-plugin": "^5.6.3", + "serve": "^14.2.4" } }