diff --git a/.github/workflows/e2e-request.yml b/.github/workflows/e2e-request.yml index 608286d..d33bc5a 100644 --- a/.github/workflows/e2e-request.yml +++ b/.github/workflows/e2e-request.yml @@ -14,9 +14,7 @@ permissions: {} jobs: request: - if: >- - github.event.label.name == 'run-e2e' && - github.event.pull_request.head.repo.full_name != github.repository + if: github.event.label.name == 'run-e2e' runs-on: ubuntu-latest timeout-minutes: 1 steps: diff --git a/.github/workflows/gitlab-e2e.yml b/.github/workflows/gitlab-e2e.yml index 3dff0d7..ce64e7f 100644 --- a/.github/workflows/gitlab-e2e.yml +++ b/.github/workflows/gitlab-e2e.yml @@ -1,15 +1,9 @@ name: GitLab E2E on: - push: - branches: [main] - tags: ["v*"] - pull_request: - branches: [main] workflow_run: workflows: [GitLab E2E request] types: [completed] - merge_group: workflow_dispatch: inputs: setup_ref: @@ -34,11 +28,11 @@ jobs: gitlab-e2e: name: GitLab E2E if: >- - github.event_name != 'workflow_run' || - (github.event.workflow_run.event == 'pull_request' && + github.event_name == 'workflow_dispatch' || + (github.event_name == 'workflow_run' && + github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.path == '.github/workflows/e2e-request.yml' && - github.event.workflow_run.head_repository.full_name != github.repository) + github.event.workflow_run.path == '.github/workflows/e2e-request.yml') runs-on: ubuntu-latest timeout-minutes: 35 outputs: @@ -53,8 +47,6 @@ jobs: id: parameters env: EVENT_NAME: ${{ github.event_name }} - EVENT_REF: ${{ github.ref }} - EVENT_REF_NAME: ${{ github.ref_name }} REQUEST_EVENT: ${{ github.event.workflow_run.event }} REQUEST_CONCLUSION: ${{ github.event.workflow_run.conclusion }} REQUEST_PATH: ${{ github.event.workflow_run.path }} @@ -62,9 +54,6 @@ jobs: REQUEST_ACTOR: ${{ github.event.workflow_run.actor.login }} REQUEST_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} REQUEST_HEAD_REPOSITORY: ${{ github.event.workflow_run.head_repository.full_name }} - PR_HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} - PR_NUMBER: ${{ github.event.pull_request.number }} MANUAL_SETUP_REF: ${{ inputs.setup_ref }} MANUAL_SUITE: ${{ inputs.suite }} MANUAL_VITE_PLUS_VERSION: ${{ inputs.vite_plus_version }} @@ -72,20 +61,18 @@ jobs: run: | set -euo pipefail - should_run=true - setup_vp_ref="$GITHUB_SHA" - suite=required + should_run=false + setup_vp_ref="" + suite=full vite_plus_version=latest report_pr_number="" - skip_reason="" + skip_reason='GitLab E2E requires a successful run-e2e label request for a PR or a manual workflow_dispatch run.' - resolve_fork_request() { - should_run=false - skip_reason='Only a successful run-e2e label request for a fork PR can approve a GitLab E2E run.' + resolve_pr_request() { local request_pattern='^PR #([1-9][0-9]*): labeled run-e2e at ([0-9a-f]{40})$' - if [ "$REQUEST_EVENT" != "pull_request" ] || [ "$REQUEST_CONCLUSION" != "success" ] || + if [ "$EVENT_NAME" != "workflow_run" ] || [ "$REQUEST_EVENT" != "pull_request" ] || + [ "$REQUEST_CONCLUSION" != "success" ] || [ "$REQUEST_PATH" != ".github/workflows/e2e-request.yml" ] || - [ "$REQUEST_HEAD_REPOSITORY" = "$GITHUB_REPOSITORY" ] || ! [[ "$REQUEST_TITLE" =~ $request_pattern ]]; then return fi @@ -114,7 +101,7 @@ jobs: trusted_blob="$(gh api "${workflow_url}?ref=${GITHUB_SHA}" --jq '.sha')" if ! request_blob="$(gh api "${workflow_url}?ref=${REQUEST_HEAD_SHA}" --jq '.sha')" || [ "$request_blob" != "$trusted_blob" ]; then - echo "::error::The fork must include e2e-request.yml unchanged from main. Update the branch, then remove and re-add run-e2e." + echo "::error::The PR must include e2e-request.yml unchanged from main. Update the branch, then remove and re-add run-e2e." exit 1 fi @@ -132,39 +119,16 @@ jobs: should_run=true setup_vp_ref="$REQUEST_HEAD_SHA" - suite=full report_pr_number="$pr_number" } - if [ "$EVENT_NAME" = "pull_request" ]; then - if [ "$PR_HEAD_REPOSITORY" != "$GITHUB_REPOSITORY" ]; then - should_run=false - skip_reason='A maintainer with write access can review this fork PR and add the run-e2e label to test its current commit. New commits require removing and re-adding the label. The merge queue also tests the reviewed merge commit.' - else - setup_vp_ref="$PR_HEAD_SHA" - report_pr_number="$PR_NUMBER" - while IFS= read -r changed_path; do - case "$changed_path" in - gitlab/* | src/gitlab/* | src/ci/* | dist/gitlab/* | .github/workflows/gitlab-e2e.yml | vite.config.ts | package.json | pnpm-lock.yaml | pnpm-workspace.yaml) - suite=full - break - ;; - esac - done < <(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" --jq '.[].filename') - fi - elif [ "$EVENT_NAME" = "workflow_run" ]; then - resolve_fork_request - elif [ "$EVENT_NAME" = "merge_group" ]; then - suite=full - elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then + if [ "$EVENT_NAME" = "workflow_dispatch" ]; then + should_run=true setup_vp_ref="${MANUAL_SETUP_REF:-$GITHUB_SHA}" suite="${MANUAL_SUITE:-full}" vite_plus_version="${MANUAL_VITE_PLUS_VERSION:-latest}" - elif [ "$EVENT_NAME" = "push" ] && [[ "$EVENT_REF" == refs/tags/* ]]; then - setup_vp_ref="$EVENT_REF_NAME" - suite=full - elif [ "$EVENT_NAME" = "push" ]; then - suite=full + else + resolve_pr_request fi { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f866990..0fb250b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,14 +52,18 @@ Use the shared primitives under [`src/ci/`](src/ci/) for portable runtime behavi ## GitLab End-to-End Tests -Use the dedicated [GitLab test project](https://gitlab.com/fengmk2/setup-vp-gitlab-test) to test the remote integration. The [GitLab E2E workflow](.github/workflows/gitlab-e2e.yml) covers same-repository pull requests, approved fork pull requests, merge queue commits, merges, and releases. The pipeline loads the template, bootstrap script, and compiled runtime from the exact setup-vp commit or release tag under test. +Use the dedicated [GitLab test project](https://gitlab.com/fengmk2/setup-vp-gitlab-test) to test the remote integration. GitLab CI has limited capacity, so the [GitLab E2E workflow](.github/workflows/gitlab-e2e.yml) runs only for pull requests approved with the `run-e2e` label or manual `workflow_dispatch` requests. The pipeline loads the template, bootstrap script, and compiled runtime from the exact approved PR commit or the manually selected commit or release tag. -### Fork Pull Requests +### Request a Run -After reviewing the commit, a maintainer with write access can add `run-e2e` to run the full GitLab suite. Approve the Actions run if prompted. +After reviewing the commit, a maintainer with write access can add `run-e2e` to run the full GitLab suite. This applies to both same-repository and fork PRs. Approve the Actions run if prompted. For each new commit, review the changes and remove and re-add `run-e2e`. Read the PR result comment for the status and GitLab pipeline link after each run. +Pushes, merge queue commits, merges, and release tags do not automatically start GitLab pipelines. + +For a manual run, use `workflow_dispatch`. Set `setup_ref` to an exact commit SHA or release tag, or leave it empty to test the selected workflow commit. Select `suite` (`full` by default) and `vite_plus_version` (`latest` by default). + ## Dependency Updates Renovate opens a PR when SocketDev publishes a new `sfw-free` release. See the custom managers in [`.github/renovate.json`](.github/renovate.json) for the pinned `SFW_VERSION` values in the GitHub and portable runtimes. @@ -85,7 +89,7 @@ Publish releases as Git tags, not as an npm package. Keep `package.json.version` git status --short # must be empty ``` -3. Confirm that the release commit on `main` passes the full GitLab E2E workflow. +3. Confirm that the release commit on `main` passes the full GitLab E2E workflow. Use `workflow_dispatch` with the exact commit SHA and `suite: full`. 4. Create the new annotated version tag and push it. For example: diff --git a/src/gitlab/workflow.test.ts b/src/gitlab/workflow.test.ts index 7fd46b2..a8a4e98 100644 --- a/src/gitlab/workflow.test.ts +++ b/src/gitlab/workflow.test.ts @@ -21,12 +21,6 @@ const steps = workflow.jobs["gitlab-e2e"].steps as Array<{ const parameters = steps.find((step) => step.id === "parameters")!; const headSha = "a".repeat(40); const baseSha = "b".repeat(40); -const approvedPr = { - state: "open", - head: { sha: headSha, repo: { full_name: "contributor/setup-vp" } }, - base: { ref: "main", repo: { full_name: "upstream/setup-vp" } }, - labels: [{ name: "run-e2e" }], -}; const tempDirs: string[] = []; type ParameterResult = SpawnSyncReturns & { @@ -62,7 +56,6 @@ case "$*" in */permission*) printf '%s\\n' "$MOCK_PERMISSION" ;; *"contents/.github/workflows/e2e-request.yml?ref=$GITHUB_SHA"*) printf '%s\\n' trusted-blob ;; *"contents/.github/workflows/e2e-request.yml?ref=$REQUEST_HEAD_SHA"*) printf '%s\\n' "$MOCK_REQUEST_BLOB" ;; - */files*) printf '%s\\n' "$MOCK_CHANGED_FILES" ;; "api repos/upstream/setup-vp/pulls/123") printf '%s\\n' "$MOCK_PR" ;; *) exit 90 ;; esac @@ -88,11 +81,6 @@ esac REQUEST_ACTOR: "reviewer", REQUEST_HEAD_SHA: headSha, REQUEST_HEAD_REPOSITORY: "contributor/setup-vp", - EVENT_REF: "refs/heads/main", - EVENT_REF_NAME: "main", - PR_HEAD_REPOSITORY: "contributor/setup-vp", - PR_HEAD_SHA: headSha, - PR_NUMBER: "123", MANUAL_SETUP_REF: "", MANUAL_SUITE: "", MANUAL_VITE_PLUS_VERSION: "", @@ -100,8 +88,6 @@ esac MOCK_GH_FAILURE: "", MOCK_PERMISSION: "write", MOCK_REQUEST_BLOB: "trusted-blob", - MOCK_PR: JSON.stringify(approvedPr), - MOCK_CHANGED_FILES: "README.md", ...overrides, }, }); @@ -120,16 +106,36 @@ esac } describe("GitLab E2E workflow", () => { - it("only accepts label events for privileged fork runs and never checks out PR code", () => { - expect(workflow.on).not.toHaveProperty("pull_request_target"); + it("only accepts PR label requests or manual runs and never checks out PR code", () => { expect(requestWorkflow.on).toEqual({ pull_request: { branches: ["main"], types: ["labeled"] }, }); expect(requestWorkflow.permissions).toEqual({}); - expect(workflow.on.workflow_run).toEqual({ - workflows: [requestWorkflow.name], - types: ["completed"], + expect(requestWorkflow.jobs.request.if).toBe("github.event.label.name == 'run-e2e'"); + expect(workflow.on).toEqual({ + workflow_run: { + workflows: [requestWorkflow.name], + types: ["completed"], + }, + workflow_dispatch: { + inputs: { + setup_ref: expect.objectContaining({ required: false, default: "" }), + suite: expect.objectContaining({ + type: "choice", + options: ["full", "required"], + default: "full", + }), + vite_plus_version: expect.objectContaining({ required: false, default: "latest" }), + }, + }, }); + expect(workflow.jobs["gitlab-e2e"].if.replace(/\s+/g, " ")).toBe( + "github.event_name == 'workflow_dispatch' || " + + "(github.event_name == 'workflow_run' && " + + "github.event.workflow_run.event == 'pull_request' && " + + "github.event.workflow_run.conclusion == 'success' && " + + "github.event.workflow_run.path == '.github/workflows/e2e-request.yml')", + ); expect(requestWorkflow["run-name"]).toBe( "PR #${{ github.event.pull_request.number }}: ${{ github.event.action }} ${{ github.event.label.name }} at ${{ github.event.pull_request.head.sha }}", ); @@ -137,137 +143,19 @@ describe("GitLab E2E workflow", () => { expect(steps.every((step) => !step.uses && !step.run.includes("${{"))).toBe(true); expect(parameters.env?.REQUEST_HEAD_SHA).toBe("${{ github.event.workflow_run.head_sha }}"); expect(parameters.env?.REQUEST_ACTOR).toBe("${{ github.event.workflow_run.actor.login }}"); + expect(parameters.env?.MANUAL_SETUP_REF).toBe("${{ inputs.setup_ref }}"); + expect(parameters.env?.MANUAL_SUITE).toBe("${{ inputs.suite }}"); + expect(parameters.env?.MANUAL_VITE_PLUS_VERSION).toBe("${{ inputs.vite_plus_version }}"); for (const step of steps.slice(1)) { expect(step.if).toBe("steps.parameters.outputs.should_run == 'true'"); } }); - it.each(["write", "admin"])( - "runs the full suite at the event SHA with %s access", - (permission) => { - const result = resolveParameters({ MOCK_PERMISSION: permission }); - expect(result.status, result.stderr).toBe(0); - expect(result.outputs).toEqual({ - should_run: "true", - setup_vp_ref: headSha, - suite: "full", - vite_plus_version: "latest", - pr_number: "123", - }); - expect(result.calls).toContain("collaborators/reviewer/permission"); - expect(result.calls).not.toContain("/files"); - }, - ); - - it.each(["read", "none", ""])("rejects approval with %s permission", (permission) => { - const result = resolveParameters({ MOCK_PERMISSION: permission }); - expect(result.status).toBe(1); - expect(result.stdout).toContain("requires repository write access"); - expect(result.outputs.should_run).toBeUndefined(); - expect(result.calls).not.toContain("/pulls/"); - }); - - it.each(["/permission", "/pulls/", `?ref=${baseSha}`, `?ref=${headSha}`])( - "fails closed when the %s API request fails", - (endpoint) => { - const result = resolveParameters({ MOCK_GH_FAILURE: endpoint }); - expect(result.status).toBe(1); - expect(result.outputs.should_run).toBeUndefined(); - }, - ); - - it.each([ - ["new commit", { head: { ...approvedPr.head, sha: "c".repeat(40) } }], - ["different fork", { head: { ...approvedPr.head, repo: { full_name: "other/setup-vp" } } }], - ["base change", { base: { ...approvedPr.base, ref: "release" } }], - [ - "different repository", - { base: { ...approvedPr.base, repo: { full_name: "other/setup-vp" } } }, - ], - ["closed PR", { state: "closed" }], - ["removed label", { labels: [{ name: "other-label" }] }], - ])("skips a stale approval after a %s", (_reason, changes) => { - const result = resolveParameters({ MOCK_PR: JSON.stringify({ ...approvedPr, ...changes }) }); - expect(result.status, result.stderr).toBe(0); - expect(result.outputs.should_run).toBe("false"); - expect(result.outputs.pr_number).toBe(""); - expect(result.summary).toContain("This approval is stale"); - }); - - it.each([ - ["unrelated label", { REQUEST_TITLE: `PR #123: labeled bug at ${headSha}` }], - ["new push", { REQUEST_TITLE: `PR #123: synchronize run-e2e at ${headSha}` }], - ["same-repository PR", { REQUEST_HEAD_REPOSITORY: "upstream/setup-vp" }], - ["different event", { REQUEST_EVENT: "push" }], - ["failed request", { REQUEST_CONCLUSION: "failure" }], - ["different workflow", { REQUEST_PATH: ".github/workflows/fake-request.yml" }], - ["malformed title", { REQUEST_TITLE: "PR #123: labeled run-e2e at $(exit 1)" }], - ])("does not approve a privileged run for a %s", (_reason, env) => { - const result = resolveParameters(env); - expect(result.status, result.stderr).toBe(0); - expect(result.outputs.should_run).toBe("false"); - expect(result.outputs.pr_number).toBe(""); - expect(result.calls).toBe(""); - }); - - it("rejects a title that claims a different commit than the request run", () => { - const result = resolveParameters({ REQUEST_TITLE: `PR #123: labeled run-e2e at ${baseSha}` }); - expect(result.status).toBe(1); - expect(result.outputs.should_run).toBeUndefined(); - expect(result.calls).toBe(""); - }); - - it("rejects a modified request workflow even with a valid title and write access", () => { - const result = resolveParameters({ MOCK_REQUEST_BLOB: "modified-blob" }); - expect(result.status).toBe(1); - expect(result.stdout).toContain("must include e2e-request.yml unchanged from main"); - expect(result.outputs.should_run).toBeUndefined(); - expect(result.calls).not.toContain("/pulls/"); - }); - - it("keeps ordinary fork PR runs unprivileged even when the approval label exists", () => { - const result = resolveParameters({ EVENT_NAME: "pull_request" }); - expect(result.status, result.stderr).toBe(0); - expect(result.outputs.should_run).toBe("false"); - expect(result.outputs.pr_number).toBe(""); - expect(result.calls).toBe(""); - expect(result.summary).toContain("add the run-e2e label"); - }); - - it.each([ - ["README.md", "required"], - ["README.md\nsrc/ci/version.ts", "full"], - ])("keeps suite selection for same-repository PRs changing %s", (files, suite) => { - const result = resolveParameters({ - EVENT_NAME: "pull_request", - PR_HEAD_REPOSITORY: "upstream/setup-vp", - MOCK_CHANGED_FILES: files, - }); - expect(result.status, result.stderr).toBe(0); - expect(result.outputs).toMatchObject({ - should_run: "true", - setup_vp_ref: headSha, - suite, - pr_number: "123", - }); - expect(result.calls).not.toContain("/permission"); - }); - it.each([ - ["push", { EVENT_NAME: "push" }, baseSha, "full", "latest"], - ["merge queue", { EVENT_NAME: "merge_group" }, baseSha, "full", "latest"], - ["manual defaults", { EVENT_NAME: "workflow_dispatch" }, baseSha, "full", "latest"], + ["defaults", {}, baseSha, "full", "latest"], [ - "release tag", - { EVENT_NAME: "push", EVENT_REF: "refs/tags/v1.19.0", EVENT_REF_NAME: "v1.19.0" }, - "v1.19.0", - "full", - "latest", - ], - [ - "manual overrides", + "overrides", { - EVENT_NAME: "workflow_dispatch", MANUAL_SETUP_REF: headSha, MANUAL_SUITE: "required", MANUAL_VITE_PLUS_VERSION: "0.3.1", @@ -276,8 +164,9 @@ describe("GitLab E2E workflow", () => { "required", "0.3.1", ], - ])("preserves %s parameters", (_name, env, ref, suite, version) => { - const result = resolveParameters(env); + ["release tag", { MANUAL_SETUP_REF: "v1.20.0" }, "v1.20.0", "full", "latest"], + ])("preserves manual run %s", (_name, env, ref, suite, version) => { + const result = resolveParameters({ EVENT_NAME: "workflow_dispatch", ...env }); expect(result.status, result.stderr).toBe(0); expect(result.outputs).toEqual({ should_run: "true", @@ -287,5 +176,125 @@ describe("GitLab E2E workflow", () => { pr_number: "", }); expect(result.calls).toBe(""); + expect(result.summary).toBe(""); }); }); + +describe.each(["upstream/setup-vp", "contributor/setup-vp"])( + "GitLab E2E approval from %s", + (headRepository) => { + const approvedPr = { + state: "open", + head: { sha: headSha, repo: { full_name: headRepository } }, + base: { ref: "main", repo: { full_name: "upstream/setup-vp" } }, + labels: [{ name: "run-e2e" }], + }; + + function resolveRequest(overrides: Record = {}): ParameterResult { + return resolveParameters({ + REQUEST_HEAD_REPOSITORY: headRepository, + MOCK_PR: JSON.stringify(approvedPr), + ...overrides, + }); + } + + it.each(["write", "admin"])( + "runs the full suite at the event SHA with %s access", + (permission) => { + const result = resolveRequest({ MOCK_PERMISSION: permission }); + expect(result.status, result.stderr).toBe(0); + expect(result.outputs).toEqual({ + should_run: "true", + setup_vp_ref: headSha, + suite: "full", + vite_plus_version: "latest", + pr_number: "123", + }); + expect(result.calls).toContain("collaborators/reviewer/permission"); + expect(result.calls).not.toContain("/files"); + }, + ); + + it.each(["read", "none", ""])("rejects approval with %s permission", (permission) => { + const result = resolveRequest({ MOCK_PERMISSION: permission }); + expect(result.status).toBe(1); + expect(result.stdout).toContain("requires repository write access"); + expect(result.outputs.should_run).toBeUndefined(); + expect(result.calls).not.toContain("/pulls/"); + }); + + it.each(["/permission", "/pulls/", `?ref=${baseSha}`, `?ref=${headSha}`])( + "fails closed when the %s API request fails", + (endpoint) => { + const result = resolveRequest({ MOCK_GH_FAILURE: endpoint }); + expect(result.status).toBe(1); + expect(result.outputs.should_run).toBeUndefined(); + }, + ); + + it.each([ + ["new commit", { head: { ...approvedPr.head, sha: "c".repeat(40) } }], + [ + "different head repository", + { head: { ...approvedPr.head, repo: { full_name: "other/setup-vp" } } }, + ], + ["base change", { base: { ...approvedPr.base, ref: "release" } }], + [ + "different repository", + { base: { ...approvedPr.base, repo: { full_name: "other/setup-vp" } } }, + ], + ["closed PR", { state: "closed" }], + ["removed label", { labels: [{ name: "other-label" }] }], + ["missing label", { labels: [] }], + ])("skips a stale approval after a %s", (_reason, changes) => { + const result = resolveRequest({ MOCK_PR: JSON.stringify({ ...approvedPr, ...changes }) }); + expect(result.status, result.stderr).toBe(0); + expect(result.outputs.should_run).toBe("false"); + expect(result.outputs.pr_number).toBe(""); + expect(result.summary).toContain("This approval is stale"); + }); + + it.each([ + ["unrelated label", { REQUEST_TITLE: `PR #123: labeled bug at ${headSha}` }], + ["new push", { REQUEST_TITLE: `PR #123: synchronize run-e2e at ${headSha}` }], + ["different event", { REQUEST_EVENT: "push" }], + ["failed request", { REQUEST_CONCLUSION: "failure" }], + ["different workflow", { REQUEST_PATH: ".github/workflows/fake-request.yml" }], + ["malformed title", { REQUEST_TITLE: "PR #123: labeled run-e2e at $(exit 1)" }], + ])("does not approve a privileged run for a %s", (_reason, env) => { + const result = resolveRequest(env); + expect(result.status, result.stderr).toBe(0); + expect(result.outputs.should_run).toBe("false"); + expect(result.outputs.pr_number).toBe(""); + expect(result.calls).toBe(""); + }); + + it("rejects a title that claims a different commit than the request run", () => { + const result = resolveRequest({ REQUEST_TITLE: `PR #123: labeled run-e2e at ${baseSha}` }); + expect(result.status).toBe(1); + expect(result.outputs.should_run).toBeUndefined(); + expect(result.calls).toBe(""); + }); + + it("rejects a modified request workflow even with a valid title and write access", () => { + const result = resolveRequest({ MOCK_REQUEST_BLOB: "modified-blob" }); + expect(result.status).toBe(1); + expect(result.stdout).toContain("must include e2e-request.yml unchanged from main"); + expect(result.outputs.should_run).toBeUndefined(); + expect(result.calls).not.toContain("/pulls/"); + }); + + it.each(["pull_request", "push", "merge_group"])( + "skips %s events even when the PR has the approval label", + (event) => { + const result = resolveRequest({ EVENT_NAME: event }); + expect(result.status, result.stderr).toBe(0); + expect(result.outputs.should_run).toBe("false"); + expect(result.outputs.setup_vp_ref).toBe(""); + expect(result.outputs.pr_number).toBe(""); + expect(result.calls).toBe(""); + expect(result.summary).toContain("GitLab E2E requires a successful run-e2e label request"); + }, + ); + }, +);