name: Userfront E2E Full Nightly on: schedule: - cron: "0 18 * * *" workflow_dispatch: permissions: contents: write jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" - name: Setup Go uses: actions/setup-go@v5 with: go-version: "1.25" cache-dependency-path: backend/go.sum - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: "stable" cache: true - name: Run common lint checks run: | make code-check-lint full-test-policy: runs-on: ubuntu-latest outputs: should_run: ${{ steps.policy.outputs.should_run }} reason: ${{ steps.policy.outputs.reason }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Decide whether full E2E is needed id: policy run: | set -euo pipefail target_sha="${GITHUB_SHA}" should_run="true" reason="manual-dispatch" if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then reason="missing-full-result" git fetch origin "+refs/heads/badges:refs/remotes/origin/badges" || true if git show-ref --verify --quiet refs/remotes/origin/badges && \ git cat-file -e "refs/remotes/origin/badges:dev/${target_sha}/badges.json" 2>/dev/null; then full_message="$( git show "refs/remotes/origin/badges:dev/${target_sha}/badges.json" | node -e "let input=''; process.stdin.on('data', c => input += c); process.stdin.on('end', () => { const data = JSON.parse(input); const keys = ['userfront-chrome', 'userfront-firefox', 'userfront-safari']; const messages = keys.map((key) => data.badges?.[key]?.message || 'unknown'); process.stdout.write(messages.join(',')); });" )" if [ -n "${full_message}" ] && ! printf '%s' "${full_message}" | grep -q "unknown"; then should_run="false" reason="full-result-exists:${full_message}" fi fi fi echo "should_run=${should_run}" >> "$GITHUB_OUTPUT" echo "reason=${reason}" >> "$GITHUB_OUTPUT" echo "target_sha=${target_sha}" echo "should_run=${should_run}" echo "reason=${reason}" userfront-e2e-full: needs: - lint - full-test-policy if: ${{ needs.lint.result == 'success' && needs.full-test-policy.outputs.should_run == 'true' }} runs-on: ubuntu-latest timeout-minutes: 80 outputs: chromium_desktop: ${{ steps.full-results.outputs.chromium_desktop }} chromium_mobile: ${{ steps.full-results.outputs.chromium_mobile }} firefox_desktop: ${{ steps.full-results.outputs.firefox_desktop }} firefox_mobile: ${{ steps.full-results.outputs.firefox_mobile }} webkit_desktop: ${{ steps.full-results.outputs.webkit_desktop }} webkit_mobile: ${{ steps.full-results.outputs.webkit_mobile }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" cache: "npm" cache-dependency-path: userfront-e2e/package-lock.json - name: Setup Flutter uses: subosito/flutter-action@v2 with: channel: "stable" cache: true - name: Sync userfront locales run: | /bin/sh ./scripts/sync_userfront_locales.sh - name: Install userfront-e2e dependencies run: | cd userfront-e2e npm ci - name: Build userfront WASM run: | cd userfront flutter build web --wasm --release cd .. node userfront/scripts/optimize-web-build.mjs userfront/build/web - name: Provision full browser matrix run: | cd userfront-e2e npx playwright install --with-deps - name: Run full userfront-e2e tests id: full-results run: | mkdir -p reports cd userfront-e2e workers="${PLAYWRIGHT_WORKERS:-4}" case "$workers" in ''|*[!0-9]*|0) workers=4 ;; esac any_failure=0 run_project() { output_name="$1" project_name="$2" log_path="../reports/userfront-e2e-full-${project_name}.log" set +e echo "[userfront-e2e-full] PLAYWRIGHT_WORKERS=${workers} npx playwright test --project=${project_name}" | tee "$log_path" PLAYWRIGHT_WORKERS="$workers" npx playwright test --project="$project_name" --reporter=list 2>&1 | tee -a "$log_path" exit_code=${PIPESTATUS[0]} set -e if [ "$exit_code" -eq 0 ]; then result="success" else result="failure" any_failure=1 fi echo "${output_name}=${result}" >> "$GITHUB_OUTPUT" } run_project chromium_desktop chromium-desktop run_project chromium_mobile chromium-mobile-webapp run_project firefox_desktop firefox-desktop echo "firefox_mobile=skipped" >> "$GITHUB_OUTPUT" run_project webkit_desktop webkit-desktop run_project webkit_mobile webkit-mobile-webapp exit "$any_failure" - name: Upload userfront-e2e full artifacts if: ${{ always() }} uses: actions/upload-artifact@v3 continue-on-error: true with: name: userfront-e2e-full-report path: | reports/userfront-e2e-full-*.log userfront-e2e/playwright-report userfront-e2e/test-results if-no-files-found: ignore badge-updater: needs: - lint - full-test-policy - userfront-e2e-full if: ${{ always() && needs.lint.result == 'success' && needs.full-test-policy.outputs.should_run == 'true' && github.ref == 'refs/heads/dev' }} runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" - name: Restore published badge state run: | git fetch origin "+refs/heads/badges:refs/remotes/origin/badges" || true if git show-ref --verify --quiet refs/remotes/origin/badges && \ git cat-file -e refs/remotes/origin/badges:latest/badges.json 2>/dev/null; then mkdir -p docs/badges git archive --format=tar refs/remotes/origin/badges latest | tar -x cp latest/* docs/badges/ rm -rf latest else echo "No published badge state found." fi - name: Update full E2E badge files env: USERFRONT_E2E_RESULT: ${{ needs.userfront-e2e-full.result }} USERFRONT_E2E_FULL: "true" USERFRONT_E2E_CHROMIUM_DESKTOP_RESULT: ${{ needs.userfront-e2e-full.outputs.chromium_desktop }} USERFRONT_E2E_CHROMIUM_MOBILE_RESULT: ${{ needs.userfront-e2e-full.outputs.chromium_mobile }} USERFRONT_E2E_FIREFOX_DESKTOP_RESULT: ${{ needs.userfront-e2e-full.outputs.firefox_desktop }} USERFRONT_E2E_FIREFOX_MOBILE_RESULT: ${{ needs.userfront-e2e-full.outputs.firefox_mobile }} USERFRONT_E2E_WEBKIT_DESKTOP_RESULT: ${{ needs.userfront-e2e-full.outputs.webkit_desktop }} USERFRONT_E2E_WEBKIT_MOBILE_RESULT: ${{ needs.userfront-e2e-full.outputs.webkit_mobile }} BADGE_UPDATE_CODE_CHECK: "false" BADGE_SOURCE_BRANCH: dev BADGE_SOURCE_SHA: ${{ github.sha }} run: | node scripts/update_code_check_badges.mjs cat docs/badges/badges.json - name: Publish full E2E badge assets run: | if [ -z "$(git status --porcelain docs/badges)" ]; then echo "No badge changes." exit 0 fi BADGE_BRANCH=badges BADGE_WORKTREE="$(mktemp -d)" BADGE_LATEST_DIR="${BADGE_WORKTREE}/latest" BADGE_SHA_DIR="${BADGE_WORKTREE}/dev/${GITHUB_SHA}" trap 'rm -rf "${BADGE_WORKTREE}"' EXIT git config user.name "gitea-actions" git config user.email "gitea-actions@hmac.kr" git fetch origin "+refs/heads/${BADGE_BRANCH}:refs/remotes/origin/${BADGE_BRANCH}" || true if git show-ref --verify --quiet "refs/remotes/origin/${BADGE_BRANCH}"; then git worktree add --detach "${BADGE_WORKTREE}" "origin/${BADGE_BRANCH}" else git worktree add --detach "${BADGE_WORKTREE}" git -C "${BADGE_WORKTREE}" checkout --orphan "${BADGE_BRANCH}" git -C "${BADGE_WORKTREE}" rm -rf . || true fi find "${BADGE_WORKTREE}" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + mkdir -p "${BADGE_LATEST_DIR}" "${BADGE_SHA_DIR}" cp docs/badges/*.svg "${BADGE_LATEST_DIR}/" cp docs/badges/badges.json "${BADGE_LATEST_DIR}/badges.json" cp docs/badges/*.svg "${BADGE_SHA_DIR}/" cp docs/badges/badges.json "${BADGE_SHA_DIR}/badges.json" git -C "${BADGE_WORKTREE}" add . if [ -z "$(git -C "${BADGE_WORKTREE}" status --porcelain)" ]; then echo "No published badge changes." exit 0 fi git -C "${BADGE_WORKTREE}" commit -m "chore: publish userfront e2e full badge [skip ci]" git -C "${BADGE_WORKTREE}" push origin HEAD:${BADGE_BRANCH}