From d2682da3cce5ca2bb4109caffa6e20ac53244c3e Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Mon, 6 Jul 2026 22:33:40 +0200 Subject: [PATCH] [codex] Improve admin notifications page UI (#145) * Improve admin notifications page UI * Stabilize notifications page E2E bootstrap wait * Fix PR Playwright diff head ref * Guard MyWashStart timers after test teardown * Stabilize self-serve wash E2E timing * Refresh edge gateway fixture runtime state * Run Playwright E2E on GitHub-hosted runners * Run all frontend CI on GitHub-hosted runners * Increase hosted full E2E parallelism * Stabilize full E2E validation * Preserve superuser gateway navigation label * Add targeted Playwright dispatch workflow * Integrate targeted Playwright dispatch into tests workflow --------- Co-authored-by: Jeppe Bundgaard --- .github/workflows/tests.yml | 229 +++++++++++++++++- scripts/playwright-pr-mapping.mjs | 5 +- .../NotificationsPhonePagination.vue | 3 +- .../notifications/DepartmentNotifications.vue | 22 +- .../admin-department-notifications.spec.ts | 17 ++ tests/e2e/i18n.views.spec.ts | 5 + tests/e2e/self-serve-wash.spec.js | 25 +- tests/e2e/support/network.js | 16 ++ tests/unit/playwright-full-workflow.spec.js | 43 +++- tests/unit/playwright-pr-mapping.spec.js | 15 +- 10 files changed, 349 insertions(+), 31 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7ff90eba..258386d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,6 +7,30 @@ on: - master workflow_dispatch: inputs: + mode: + description: "What to run for a manual dispatch." + required: true + type: choice + default: full + options: + - full + - targeted + - targeted-then-full + target_specs: + description: "Comma- or newline-separated Playwright spec paths under tests/e2e." + required: false + type: string + default: "tests/e2e/superuser-department-overview.spec.js" + target_projects: + description: "JSON array of Playwright projects for targeted mode." + required: false + type: string + default: '["chromium-desktop","chromium-mobile","chromium-tablet","webkit-mobile","webkit-desktop"]' + target_grep: + description: "Optional Playwright grep pattern for targeted mode." + required: false + type: string + default: "" runner: description: "Runner pool for this manually dispatched test run" required: false @@ -31,7 +55,7 @@ jobs: runs-on: ${{ fromJSON(github.event_name == 'workflow_dispatch' && github.event.inputs.runner == 'github-hosted' && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","pleno","frontend"]') }} timeout-minutes: 15 steps: - - name: Repair self-hosted workspace permissions + - name: Normalize workspace permissions shell: bash run: | if [[ -d "$GITHUB_WORKSPACE" ]]; then @@ -67,7 +91,7 @@ jobs: runs-on: ${{ fromJSON(github.event_name == 'workflow_dispatch' && github.event.inputs.runner == 'github-hosted' && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","pleno","frontend"]') }} timeout-minutes: 30 steps: - - name: Repair self-hosted workspace permissions + - name: Normalize workspace permissions shell: bash run: | if [[ -d "$GITHUB_WORKSPACE" ]]; then @@ -106,11 +130,192 @@ jobs: env: VITEST_BATCH_SIZE: 5 - e2e-pr: - if: github.event_name != 'schedule' + e2e-targeted: + if: > + github.event_name == 'workflow_dispatch' && + (inputs.mode == 'targeted' || inputs.mode == 'targeted-then-full') needs: build-and-unit + name: E2E-targeted-${{ matrix.project }} + # Use GitHub-hosted runners to avoid self-hosted desktop contention and sleep/power events. + runs-on: ubuntu-24.04 + timeout-minutes: 35 + strategy: + fail-fast: false + matrix: + project: ${{ fromJSON(inputs.target_projects || '["chromium-desktop"]') }} + env: + MATRIX_PROJECT: ${{ matrix.project }} + PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-targeted-${{ matrix.project }} + PLAYWRIGHT_REPORTER_MODE: line-html + PLAYWRIGHT_WORKERS: 1 + PLAYWRIGHT_VIDEO_MODE: on-first-retry + TARGET_GREP: ${{ inputs.target_grep }} + TARGET_SPECS: ${{ inputs.target_specs }} + RUN_ID: ${{ github.run_id }} + steps: + - name: Normalize workspace permissions + shell: bash + run: | + if [[ -d "$GITHUB_WORKSPACE" ]]; then + sudo -n chown -R "$(id -u):$(id -g)" "$GITHUB_WORKSPACE" 2>/dev/null || true + foreign_entry="$(find "$GITHUB_WORKSPACE" -mindepth 1 -maxdepth 2 ! -user "$(id -u)" -print -quit 2>/dev/null || true)" + if [[ -n "$foreign_entry" ]]; then + trash="$GITHUB_WORKSPACE/../_workspace-trash-$GITHUB_RUN_ID-$GITHUB_JOB" + rm -rf "$trash" 2>/dev/null || true + mv "$GITHUB_WORKSPACE" "$trash" 2>/dev/null || true + mkdir -p "$GITHUB_WORKSPACE" + fi + fi + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + + - name: Run targeted Playwright specs in container + shell: bash + run: | + set -euo pipefail + case "$MATRIX_PROJECT" in + chromium-mobile) project_offset=1 ;; + chromium-desktop) project_offset=2 ;; + chromium-tablet) project_offset=3 ;; + webkit-mobile) project_offset=31 ;; + webkit-desktop) project_offset=32 ;; + webkit-tablet) project_offset=33 ;; + firefox-mobile) project_offset=61 ;; + firefox-desktop) project_offset=62 ;; + firefox-tablet) project_offset=63 ;; + *) echo "Unsupported Playwright project: $MATRIX_PROJECT" >&2; exit 1 ;; + esac + port_seed=$((20000 + (RUN_ID % 20000) + project_offset)) + lock_root="${PLAYWRIGHT_PORT_LOCK_ROOT:-/tmp/pleno-playwright-port-locks}" + mkdir -p "$lock_root" + chmod 1777 "$lock_root" 2>/dev/null || true + find "$lock_root" -mindepth 1 -maxdepth 1 -type d -mmin +360 -exec rmdir {} \; 2>/dev/null || true + playwright_port_lock="" + playwright_dev_port="" + for ((candidate = port_seed; candidate < port_seed + 1000; candidate += 1)); do + lock_dir="${lock_root}/${candidate}.lock" + if ! mkdir "$lock_dir" 2>/dev/null; then + continue + fi + if ss -H -ltn "sport = :${candidate}" 2>/dev/null | grep -q .; then + rmdir "$lock_dir" || true + continue + fi + playwright_port_lock="$lock_dir" + playwright_dev_port="$candidate" + break + done + if [[ -z "$playwright_dev_port" ]]; then + echo "Unable to find a free Playwright dev-server port." >&2 + exit 1 + fi + trap 'if [[ -n "${playwright_port_lock:-}" ]]; then rmdir "$playwright_port_lock" 2>/dev/null || true; fi' EXIT + if docker info >/dev/null 2>&1; then + docker_cmd=(docker) + elif sudo -n docker info >/dev/null 2>&1; then + docker_cmd=(sudo docker) + else + echo "Docker is not available to the runner user, and sudo docker is not available." >&2 + exit 1 + fi + mkdir -p output/playwright + scripts/ci/runner-diagnostics.sh "before targeted Playwright ${MATRIX_PROJECT}" -- "${docker_cmd[@]}" + SYSTEMD_INHIBIT_REASON="Frontend targeted Playwright ${MATRIX_PROJECT}" \ + scripts/ci/with-systemd-inhibit.sh "${docker_cmd[@]}" run --rm --ipc=host --network host \ + --volume "$PWD:/source:ro" \ + --volume "$PWD/output/playwright:/work/output/playwright" \ + --workdir /work \ + --env HOME=/tmp \ + --env CI="${CI:-}" \ + --env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \ + --env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \ + --env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \ + --env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \ + --env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \ + --env MATRIX_PROJECT="$MATRIX_PROJECT" \ + --env TARGET_GREP="$TARGET_GREP" \ + --env TARGET_SPECS="$TARGET_SPECS" \ + mcr.microsoft.com/playwright:v1.58.2-noble \ + bash -lc ' + set -euo pipefail + tar --exclude=./output/playwright -C /source -cf - . | tar -C /work -xf - + git config --global --add safe.directory /work + install_dependencies() { + local attempt + for attempt in 1 2 3; do + if npm ci --legacy-peer-deps --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000; then + return 0 + fi + if [[ "$attempt" == "3" ]]; then + return 1 + fi + echo "npm ci failed on attempt ${attempt}; retrying..." >&2 + sleep 20 + done + } + install_dependencies + ulimit -n 16384 || true + mapfile -t spec_args < <(printf "%s\n" "$TARGET_SPECS" | tr "," "\n" | sed "s/^[[:space:]]*//;s/[[:space:]]*$//;/^$/d") + if [[ "${#spec_args[@]}" -eq 0 && -z "${TARGET_GREP:-}" ]]; then + echo "Provide at least one spec path or grep pattern." >&2 + exit 1 + fi + for spec_path in "${spec_args[@]}"; do + if [[ "$spec_path" == /* || "$spec_path" == *".."* || "$spec_path" != tests/e2e/* ]]; then + echo "Targeted spec must stay under tests/e2e: $spec_path" >&2 + exit 1 + fi + if [[ ! -f "$spec_path" ]]; then + echo "Targeted spec does not exist: $spec_path" >&2 + exit 1 + fi + done + args=("${spec_args[@]}") + if [[ -n "${TARGET_GREP:-}" ]]; then + args+=(--grep "$TARGET_GREP") + fi + args+=(--project="$MATRIX_PROJECT") + npx playwright test "${args[@]}" + ' + + - name: Runner diagnostics after Playwright failure + if: failure() || cancelled() + continue-on-error: true + run: scripts/ci/runner-diagnostics.sh "after targeted Playwright ${{ matrix.project }}" + + - name: Upload Playwright report + if: failure() || cancelled() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: playwright-report-targeted-${{ matrix.project }} + path: | + output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}-* + output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }} + if-no-files-found: ignore + retention-days: 1 + + e2e-pr: + if: > + always() && + github.event_name != 'schedule' && + needs.build-and-unit.result == 'success' && + !(github.event_name == 'workflow_dispatch' && inputs.mode == 'targeted') && + ( + github.event_name != 'workflow_dispatch' || + inputs.mode == 'full' || + needs.e2e-targeted.result == 'success' + ) + needs: [build-and-unit, e2e-targeted] name: E2E-pr-${{ matrix.suite }}-${{ matrix.project }} - runs-on: ubuntu-latest + # Use GitHub-hosted runners to avoid self-hosted desktop contention and sleep/power events. + runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: fail-fast: false @@ -124,7 +329,7 @@ jobs: PLAYWRIGHT_WORKERS: 1 PLAYWRIGHT_VIDEO_MODE: on-first-retry steps: - - name: Repair self-hosted workspace permissions + - name: Normalize workspace permissions shell: bash run: | if [[ -d "$GITHUB_WORKSPACE" ]]; then @@ -300,9 +505,15 @@ jobs: if: > always() && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.ref_name == github.event.repository.default_branch) && + !(github.event_name == 'workflow_dispatch' && inputs.mode == 'targeted') && needs.build-and-unit.result == 'success' && - (github.event_name == 'schedule' || needs.e2e-pr.result == 'success') - needs: [build-and-unit, e2e-pr] + (github.event_name == 'schedule' || needs.e2e-pr.result == 'success') && + ( + github.event_name != 'workflow_dispatch' || + inputs.mode == 'full' || + needs.e2e-targeted.result == 'success' + ) + needs: [build-and-unit, e2e-pr, e2e-targeted] name: E2E-full-${{ matrix.browser_label }}-${{ matrix.device }}-${{ matrix.role }} runs-on: ${{ fromJSON(github.event_name == 'workflow_dispatch' && github.event.inputs.runner == 'github-hosted' && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","pleno","frontend","docker"]') }} timeout-minutes: 60 @@ -330,7 +541,7 @@ jobs: PLAYWRIGHT_WORKERS: 1 PLAYWRIGHT_VIDEO_MODE: off steps: - - name: Repair self-hosted workspace permissions + - name: Normalize workspace permissions shell: bash run: | if [[ -d "$GITHUB_WORKSPACE" ]]; then diff --git a/scripts/playwright-pr-mapping.mjs b/scripts/playwright-pr-mapping.mjs index adb53298..f197c1ba 100644 --- a/scripts/playwright-pr-mapping.mjs +++ b/scripts/playwright-pr-mapping.mjs @@ -96,8 +96,8 @@ export const sourceMappings = [ { name: "pos", patterns: [ - /\/pos[/-]/iu, - /POS/iu, + /(?:^|[/_.-])pos(?:[/_.-]|$)/iu, + /(?:^|\/)(?:POS|Pos)[A-Z][^/]*\.(?:vue|js|ts)$/u, /^src\/assets\/pos\.css$/u, /^src\/components\/displays\/boxes\/ProductBox\.vue$/u, /^src\/features\/customer\/customerProductRules\.js$/u, @@ -120,6 +120,7 @@ export const sourceMappings = [ { name: "admin-department-notifications", patterns: [ + /^src\/views\/dashboards\/departmentDashboard\/modules\/notifications\/DepartmentNotifications\.vue$/u, /^src\/components\/displays\/department\/notifications\//u, /^src\/components\/displays\/pagination\/models\/DepartmentPos\/NotificationsPhonePagination\.vue$/u, ], diff --git a/src/components/displays/pagination/models/DepartmentPos/NotificationsPhonePagination.vue b/src/components/displays/pagination/models/DepartmentPos/NotificationsPhonePagination.vue index fcb6ccb9..1c70f0c3 100644 --- a/src/components/displays/pagination/models/DepartmentPos/NotificationsPhonePagination.vue +++ b/src/components/displays/pagination/models/DepartmentPos/NotificationsPhonePagination.vue @@ -38,7 +38,8 @@ if (router.currentRoute.value.params.departmentId) {