name: E2E Tests
on:
  push:
    branches:
      - main
  pull_request:
  schedule:
    - cron: 0 * * * *  # hourly

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

permissions:
  contents: write
  pull-requests: write
  actions: read

jobs:

  prepare-wasm:
    # separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
    runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
    steps:
      - uses: actions/checkout@v4

      - id: filter
        name: Check for Rust changes
        uses: dorny/paths-filter@v3
        with:
          filters: |
            rust:
              - 'rust/**'

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'npm'

      - name: Install dependencies
        run: npm install

      - name: Download Wasm cache
        id: download-wasm
        if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
        uses: dawidd6/action-download-artifact@v7
        continue-on-error: true
        with:
          github_token: ${{secrets.GITHUB_TOKEN}}
          name: wasm-bundle
          workflow: build-and-store-wasm.yml
          branch: main
          path: rust/kcl-wasm-lib/pkg

      - name: Build Wasm condition
        id: wasm
        run: |
          set -euox pipefail
          # Build wasm if this is a scheduled run, there are Rust changes, or
          # downloading from the wasm cache failed.
          if [[ ${{github.event_name}} == 'schedule' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
            echo "should-build-wasm=true" >> $GITHUB_OUTPUT
          else
            echo "should-build-wasm=false" >> $GITHUB_OUTPUT
          fi

      - name: Use correct Rust toolchain
        if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
        shell: bash
        run: |
          [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./

      - name: Install Rust
        if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          cache: false # Configured below.

      - uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
        if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
        with:
          tool: wasm-pack

      - name: Use Rust cache
        if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
        uses: Swatinem/rust-cache@v2
        with:
          workspaces: './rust'

      - name: Build Wasm
        if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
        shell: bash
        run: npm run build:wasm

      - uses: actions/upload-artifact@v4
        with:
          name: prepared-wasm
          path: |
            rust/kcl-wasm-lib/pkg/kcl_wasm_lib*

  snapshots:
    name: playwright:snapshots:ubuntu
    runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
    needs: [prepare-wasm]
    steps:
      - uses: actions/create-github-app-token@v1
        id: app-token
        with:
          app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
          private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}

      - uses: actions/checkout@v4
        with:
          token: ${{ steps.app-token.outputs.token }}

      - uses: actions/download-artifact@v4
        name: prepared-wasm

      - name: Copy prepared Wasm
        run: |
          ls -R prepared-wasm
          cp prepared-wasm/kcl_wasm_lib_bg.wasm public
          mkdir rust/kcl-wasm-lib/pkg
          cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'npm'

      - name: Install dependencies
        id: deps-install
        run: npm install

      - name: Cache browsers
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/ms-playwright/
          key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}

      - name: Install browsers
        run: npm run playwright install --with-deps

      - name: Test snapshots
        uses: nick-fields/retry@v3.0.2
        with:
          shell: bash
          command: npm run test:snapshots
          timeout_minutes: 5
          max_attempts: 5
        env:
          token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
          TAB_API_URL: ${{ secrets.TAB_API_URL }}
          TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
          CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
          CI_PR_NUMBER: ${{ github.event.pull_request.number }}
          CI_SUITE: snapshots
          TARGET: web

      - name: Update snapshots
        if: always()
        run: npm run test:snapshots -- --last-failed --update-snapshots
        env:
          token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
          TAB_API_URL: ${{ secrets.TAB_API_URL }}
          TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
          CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
          CI_PR_NUMBER: ${{ github.event.pull_request.number }}
          CI_SUITE: snapshots
          TARGET: web

      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() && (success() || failure()) }}
        with:
          name: playwright-report-ubuntu-snapshot-${{ github.sha }}
          path: playwright-report/
          include-hidden-files: true
          retention-days: 30
          overwrite: true

      - name: Check diff
        if: ${{ github.ref != 'refs/heads/main' }}
        shell: bash
        id: git-check
        run: |
            git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
            if git status | grep --quiet "Changes to be committed"
            then echo "modified=true" >> $GITHUB_OUTPUT
            else echo "modified=false" >> $GITHUB_OUTPUT
            fi

      - name: Commit changes
        if: ${{ steps.git-check.outputs.modified == 'true' }}
        shell: bash
        run: |
          git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
          git fetch origin
          echo ${{ github.head_ref }}
          git checkout ${{ github.head_ref }}
          git commit --message "Update snapshots" || true
          git push
          git push origin ${{ github.head_ref }}

  electron:
    needs: [prepare-wasm]
    timeout-minutes: 60
    env:
      OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
    name: playwright:electron:${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }} (shard ${{ matrix.shardIndex }})
    strategy:
      fail-fast: false
      matrix:
        # TODO: enable namespace-profile-windows-latest once available
        include:
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 1
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 2
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 3
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 4
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 5
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 6
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 7
            shardTotal: 8
          - os: "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
            shardIndex: 8
            shardTotal: 8
          - os: namespace-profile-macos-8-cores
            shardIndex: 1
            shardTotal: 2
          - os: namespace-profile-macos-8-cores
            shardIndex: 2
            shardTotal: 2
          - os: windows-latest-8-cores
            shardIndex: 1
            shardTotal: 2
          - os: windows-latest-8-cores
            shardIndex: 2
            shardTotal: 2
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/download-artifact@v4
        name: prepared-wasm

      - name: Copy prepared wasm
        run: |
          ls -R prepared-wasm
          cp prepared-wasm/kcl_wasm_lib_bg.wasm public
          mkdir rust/kcl-wasm-lib/pkg
          cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg

      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
          cache: 'npm'

      - name: Install dependencies
        id: deps-install
        run: npm install

      - name: Cache Playwright Browsers
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/ms-playwright/
          key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright Browsers
        run: npm run playwright install --with-deps

      - name: Build web
        run: npm run tronb:vite:dev

      - name: Start Vector
        if: ${{ !contains(matrix.os, 'windows') }}
        run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
        env:
          GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
          OS_NAME: ${{ env.OS_NAME }}

      - uses: actions/download-artifact@v4
        if: ${{ !cancelled() && (success() || failure()) }}
        continue-on-error: true
        with:
          name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
          path: test-results/

      - name: Run playwright/electron flow (with retries)
        id: retry
        if:  ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
        uses: nick-fields/retry@v3.0.2
        with:
          shell: bash
          command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{ env.OS_NAME }}
          timeout_minutes: 30
          max_attempts: 9
        env:
          FAIL_ON_CONSOLE_ERRORS: true
          token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
          TAB_API_URL: ${{ secrets.TAB_API_URL }}
          TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
          CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
          CI_PR_NUMBER: ${{ github.event.pull_request.number }}
          TARGET: desktop

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
          path: test-results/
          include-hidden-files: true
          retention-days: 30
          overwrite: true

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
          path: playwright-report/
          include-hidden-files: true
          retention-days: 30
          overwrite: true
