Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/actions/plan-e2e/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ outputs:
playwright-config:
description: Playwright config path relative to working-directory.
value: ${{ steps.plan.outputs.playwright-config }}
compose-file:
description: Docker Compose file relative to the repository root.
value: ${{ steps.plan.outputs.compose-file }}
base-url:
description: Ready SUT URL used by Playwright.
value: ${{ steps.plan.outputs.base-url }}
Expand Down
12 changes: 4 additions & 8 deletions .github/actions/plan-e2e/plan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ def append_output(file, name, value)
playwright_config = require_relative_path(playwright["config"], "playwright.config")

sut = require_mapping(manifest["sut"], "sut")
reject_unknown_keys(sut, %w[composeFile baseUrl], "sut")
compose_file = require_relative_path(sut["composeFile"], "sut.composeFile")
reject_unknown_keys(sut, %w[baseUrl], "sut")
begin
base_url = URI(sut["baseUrl"].to_s)
rescue URI::InvalidURIError
Expand Down Expand Up @@ -163,17 +162,15 @@ def append_output(file, name, value)

dockerfile_path = working_directory_path.join(dockerfile).cleanpath
playwright_config_path = working_directory_path.join(playwright_config).cleanpath
compose_path = workspace.join(compose_file).cleanpath
[
required_paths = [
[dockerfile_path, "runner.dockerfile"],
[playwright_config_path, "playwright.config"],
[compose_path, "sut.composeFile"],
].each do |path, label|
]
required_paths.each do |path, label|
fail_plan("#{label} does not exist: #{path}") unless path.file?
end
fail_plan("runner.dockerfile must remain inside the E2E directory") unless dockerfile_path.realpath.to_s.start_with?("#{working_directory_path}/")
fail_plan("playwright.config must remain inside the E2E directory") unless playwright_config_path.realpath.to_s.start_with?("#{working_directory_path}/")
fail_plan("sut.composeFile must remain inside the repository") unless compose_path.realpath.to_s.start_with?("#{workspace}/")

files_output, files_error, files_status = Open3.capture3(
"git",
Expand Down Expand Up @@ -218,7 +215,6 @@ def append_output(file, name, value)
append_output(output_file, "working-directory", working_directory)
append_output(output_file, "dockerfile", dockerfile)
append_output(output_file, "playwright-config", playwright_config)
append_output(output_file, "compose-file", compose_file)
append_output(output_file, "base-url", base_url.to_s)
append_output(output_file, "projects", projects.join("\n"))
append_output(output_file, "labels", labels.join("\n"))
Expand Down
90 changes: 4 additions & 86 deletions .github/actions/run-e2e/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,9 @@ inputs:
description: Path to the Playwright configuration, relative to working-directory.
required: false
default: playwright.config.ts
compose-file:
description: Docker Compose file for the SUT, relative to the repository root.
required: false
default: compose.e2e.yml
base-url:
description: Base URL exposed by the Dockerized SUT.
required: false
default: http://127.0.0.1:4173
description: HTTP or HTTPS base URL of the ready, caller-managed SUT.
required: true
runner-image-name:
description: Exact local Docker image reference loaded from the runner artifact.
required: true
Expand Down Expand Up @@ -58,7 +53,7 @@ outputs:
description: "Shard result: passed, failed, or infrastructure-error."
value: ${{ steps.finalize.outputs.result }}
exit-code:
description: Effective shard exit code after SUT, Playwright, results, and cleanup checks.
description: Effective shard exit code after runner verification and Playwright execution.
value: ${{ steps.finalize.outputs.exit-code }}
shard-directory:
description: Absolute path containing this shard's results.
Expand All @@ -76,7 +71,6 @@ runs:
env:
E2E_WORKING_DIRECTORY: ${{ inputs.working-directory }}
E2E_PLAYWRIGHT_CONFIG: ${{ inputs.playwright-config }}
E2E_COMPOSE_FILE: ${{ inputs.compose-file }}
E2E_BASE_URL: ${{ inputs.base-url }}
E2E_RUNNER_IMAGE_NAME: ${{ inputs.runner-image-name }}
E2E_RUNNER_IMAGE_ID: ${{ inputs.runner-image-id }}
Expand All @@ -95,7 +89,6 @@ runs:
"$GITHUB_WORKSPACE" \
"$E2E_WORKING_DIRECTORY" \
"$E2E_PLAYWRIGHT_CONFIG" \
"$E2E_COMPOSE_FILE" \
"$E2E_BASE_URL" \
"$E2E_RUNNER_IMAGE_NAME" \
"$E2E_RUNNER_IMAGE_ID" \
Expand All @@ -111,9 +104,7 @@ runs:
{
echo "working-directory=$E2E_ABSOLUTE_WORKING_DIRECTORY"
echo "config=$E2E_CONTAINER_CONFIG"
echo "compose-file=$E2E_ABSOLUTE_COMPOSE_FILE"
echo "base-url=$E2E_BASE_URL"
echo "compose-project=$E2E_COMPOSE_PROJECT"
echo "runner-image-name=$E2E_RUNNER_IMAGE_NAME"
echo "runner-image-id=$E2E_RUNNER_IMAGE_ID"
echo "selection-file=$E2E_SELECTION_FILE"
Expand Down Expand Up @@ -153,36 +144,11 @@ runs:
} >> "$GITHUB_OUTPUT"
exit "$runner_exit_code"

- name: Start Dockerized SUT
id: start
if: >-
always() && steps.initialize.outcome == 'success' &&
steps.runner.outputs.exit-code == '0'
continue-on-error: true
shell: bash
env:
E2E_COMPOSE_FILE: ${{ steps.initialize.outputs.compose-file }}
E2E_COMPOSE_PROJECT: ${{ steps.initialize.outputs.compose-project }}
E2E_SHARD_DIRECTORY: ${{ steps.initialize.outputs.shard-directory }}
run: |
set +e
set -uo pipefail
startup_log="$E2E_SHARD_DIRECTORY/docker-compose-startup.log"
docker compose \
--file "$E2E_COMPOSE_FILE" \
--project-name "$E2E_COMPOSE_PROJECT" \
up --detach --build --wait > "$startup_log" 2>&1
startup_exit_code=$?
cat "$startup_log"
echo "exit-code=$startup_exit_code" >> "$GITHUB_OUTPUT"
exit "$startup_exit_code"

- name: Run Playwright shard
id: playwright
if: >-
steps.initialize.outcome == 'success' &&
steps.runner.outputs.exit-code == '0' &&
steps.start.outputs.exit-code == '0'
steps.runner.outputs.exit-code == '0'
continue-on-error: true
shell: bash
env:
Expand Down Expand Up @@ -243,48 +209,6 @@ runs:
fi
exit "$playwright_exit_code"

- name: Capture Dockerized SUT logs
id: logs
if: always() && steps.initialize.outcome == 'success'
continue-on-error: true
shell: bash
env:
E2E_COMPOSE_FILE: ${{ steps.initialize.outputs.compose-file }}
E2E_COMPOSE_PROJECT: ${{ steps.initialize.outputs.compose-project }}
E2E_SHARD_DIRECTORY: ${{ steps.initialize.outputs.shard-directory }}
run: |
set +e
service_log="$E2E_SHARD_DIRECTORY/docker-compose.log"
docker compose \
--file "$E2E_COMPOSE_FILE" \
--project-name "$E2E_COMPOSE_PROJECT" \
logs --no-color --timestamps > "$service_log" 2>&1
log_exit_code=$?
cat "$service_log"
echo "exit-code=$log_exit_code" >> "$GITHUB_OUTPUT"
exit "$log_exit_code"

- name: Tear down Dockerized SUT
id: teardown
if: always() && steps.initialize.outcome == 'success'
continue-on-error: true
shell: bash
env:
E2E_COMPOSE_FILE: ${{ steps.initialize.outputs.compose-file }}
E2E_COMPOSE_PROJECT: ${{ steps.initialize.outputs.compose-project }}
E2E_SHARD_DIRECTORY: ${{ steps.initialize.outputs.shard-directory }}
run: |
set +e
teardown_log="$E2E_SHARD_DIRECTORY/docker-compose-teardown.log"
docker compose \
--file "$E2E_COMPOSE_FILE" \
--project-name "$E2E_COMPOSE_PROJECT" \
down --volumes --remove-orphans > "$teardown_log" 2>&1
teardown_exit_code=$?
cat "$teardown_log"
echo "exit-code=$teardown_exit_code" >> "$GITHUB_OUTPUT"
exit "$teardown_exit_code"

- name: Finalize shard results
id: finalize
if: always() && steps.initialize.outcome == 'success'
Expand All @@ -295,11 +219,8 @@ runs:
E2E_RUNNER_LOAD_OUTCOME: ${{ inputs.runner-load-outcome }}
E2E_RUNNER_EXIT_CODE: ${{ steps.runner.outputs.exit-code }}
E2E_ACTUAL_RUNNER_IMAGE_ID: ${{ steps.runner.outputs.actual-image-id }}
E2E_STARTUP_EXIT_CODE: ${{ steps.start.outputs.exit-code }}
E2E_PLAYWRIGHT_EXIT_CODE: ${{ steps.playwright.outputs.exit-code }}
E2E_PLAYWRIGHT_FAILURE_KIND: ${{ steps.playwright.outputs.failure-kind }}
E2E_LOG_EXIT_CODE: ${{ steps.logs.outputs.exit-code }}
E2E_TEARDOWN_EXIT_CODE: ${{ steps.teardown.outputs.exit-code }}
run: |
set +e
node "$GITHUB_ACTION_PATH/complete-shard.mjs" \
Expand All @@ -308,11 +229,8 @@ runs:
"$E2E_RUNNER_LOAD_OUTCOME" \
"$E2E_RUNNER_EXIT_CODE" \
"$E2E_ACTUAL_RUNNER_IMAGE_ID" \
"$E2E_STARTUP_EXIT_CODE" \
"$E2E_PLAYWRIGHT_EXIT_CODE" \
"$E2E_PLAYWRIGHT_FAILURE_KIND" \
"$E2E_LOG_EXIT_CODE" \
"$E2E_TEARDOWN_EXIT_CODE" \
"$GITHUB_OUTPUT"
completion_exit_code=$?
exit "$completion_exit_code"
71 changes: 4 additions & 67 deletions .github/actions/run-e2e/complete-shard.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ const [
runnerLoadOutcome,
requestedRunnerExitCode,
actualRunnerImageId,
requestedStartupExitCode,
requestedPlaywrightExitCode,
requestedPlaywrightFailureKind,
requestedLogExitCode,
requestedTeardownExitCode,
outputFile,
] = process.argv.slice(2);
const parseExitCode = (requestedValue, label) => {
Expand All @@ -23,10 +20,6 @@ const parseExitCode = (requestedValue, label) => {
return value;
};

const startupExitCode = parseExitCode(
requestedStartupExitCode,
"startup",
);
const runnerExitCode = parseExitCode(
requestedRunnerExitCode,
"runner image verification",
Expand All @@ -35,11 +28,6 @@ const playwrightExitCode = parseExitCode(
requestedPlaywrightExitCode,
"Playwright",
);
const logExitCode = parseExitCode(requestedLogExitCode, "log capture");
const teardownExitCode = parseExitCode(
requestedTeardownExitCode,
"teardown",
);

const status = JSON.parse(readFileSync(statusFile, "utf8"));
const finishedAt = new Date();
Expand Down Expand Up @@ -78,25 +66,8 @@ if (runnerTransportSucceeded && runnerExitCode === null) {
if (
runnerTransportSucceeded &&
runnerExitCode === 0 &&
startupExitCode === null
) {
failures.push({
kind: "infrastructure",
phase: "sut-startup",
message: "Dockerized SUT startup did not report an exit code",
});
} else if (
runnerTransportSucceeded &&
runnerExitCode === 0 &&
startupExitCode !== 0
playwrightExitCode === null
) {
failures.push({
kind: "infrastructure",
phase: "sut-startup",
message: `Dockerized SUT startup exited with ${startupExitCode}`,
});
}
if (runnerExitCode === 0 && startupExitCode === 0 && playwrightExitCode === null) {
failures.push({
kind: "infrastructure",
phase: "playwright-execution",
Expand Down Expand Up @@ -125,33 +96,6 @@ if (runnerExitCode === 0 && startupExitCode === 0 && playwrightExitCode === null
message: `Playwright exited with ${playwrightExitCode}`,
});
}
if (logExitCode === null) {
failures.push({
kind: "infrastructure",
phase: "results",
message: "Docker Compose log capture did not report an exit code",
});
} else if (logExitCode !== 0) {
failures.push({
kind: "infrastructure",
phase: "results",
message: `Docker Compose log capture exited with ${logExitCode}`,
});
}
if (teardownExitCode === null) {
failures.push({
kind: "infrastructure",
phase: "cleanup",
message: "Docker Compose teardown did not report an exit code",
});
} else if (teardownExitCode !== 0) {
failures.push({
kind: "infrastructure",
phase: "cleanup",
message: `Docker Compose teardown exited with ${teardownExitCode}`,
});
}

const testFailure = failures.find((failure) => failure.kind === "test");
const infrastructureFailure = failures.find(
(failure) => failure.kind === "infrastructure",
Expand All @@ -174,12 +118,6 @@ const exitCodeForFailure = (failure) => {
return playwrightExitCode ?? 1;
case "runner-image":
return runnerExitCode ?? 1;
case "sut-startup":
return startupExitCode ?? 1;
case "results":
return logExitCode ?? 1;
case "cleanup":
return teardownExitCode ?? 1;
default:
return failure ? 1 : 0;
}
Expand All @@ -188,13 +126,10 @@ const effectiveExitCode = exitCodeForFailure(status.primaryFailure);
status.exitCode = effectiveExitCode;
status.lifecycle.runner.verificationExitCode = runnerExitCode;
status.lifecycle.runner.actualImageId = actualRunnerImageId || null;
status.lifecycle.sut.startupExitCode = startupExitCode;
status.lifecycle.playwright = {
exitCode: playwrightExitCode,
started: playwrightExitCode !== null,
};
status.lifecycle.results.logCaptureExitCode = logExitCode;
status.lifecycle.cleanup.teardownExitCode = teardownExitCode;
status.finishedAt = finishedAt.toISOString();
status.durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());

Expand All @@ -210,7 +145,9 @@ for (const [name, value] of [
}

if (status.primaryFailure) {
console.error(`${status.primaryFailure.kind}: ${status.primaryFailure.message}`);
console.error(
`${status.primaryFailure.kind}: ${status.primaryFailure.message}`,
);
}
for (const failure of status.secondaryFailures) {
console.error(`additional ${failure.kind}: ${failure.message}`);
Expand Down
Loading
Loading