mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
chore(ci): add unified test report PR comment
Change-Id: I1cee5c74dcff06215bf8f75b307a2d296a6a6964
This commit is contained in:
@@ -0,0 +1,395 @@
|
|||||||
|
import { readFileSync, appendFileSync, readdirSync, existsSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { parseArgs } from "node:util";
|
||||||
|
|
||||||
|
const { values } = parseArgs({
|
||||||
|
options: {
|
||||||
|
json: { type: "string" },
|
||||||
|
name: { type: "string" },
|
||||||
|
framework: { type: "string", default: "vitest" },
|
||||||
|
coverage: { type: "string" },
|
||||||
|
"pr-comment": { type: "boolean", default: false },
|
||||||
|
"artifacts-dir": { type: "string" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function readJson(path) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(readFileSync(path, "utf8"));
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(milliseconds) {
|
||||||
|
if (milliseconds < 1000) {
|
||||||
|
return `${milliseconds}ms`;
|
||||||
|
}
|
||||||
|
const seconds = milliseconds / 1000;
|
||||||
|
if (seconds < 60) {
|
||||||
|
return `${seconds.toFixed(1)}s`;
|
||||||
|
}
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = (seconds % 60).toFixed(0);
|
||||||
|
return `${minutes}m ${remainingSeconds}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVitestResults(data) {
|
||||||
|
const startTime = data.startTime ?? 0;
|
||||||
|
const endTime = Math.max(
|
||||||
|
...(data.testResults ?? []).map((r) => r.endTime ?? 0),
|
||||||
|
startTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: data.numTotalTests ?? 0,
|
||||||
|
passed: data.numPassedTests ?? 0,
|
||||||
|
failed: data.numFailedTests ?? 0,
|
||||||
|
skipped: data.numPendingTests ?? 0,
|
||||||
|
flaky: 0,
|
||||||
|
duration: endTime - startTime,
|
||||||
|
success: data.success ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePlaywrightResults(data) {
|
||||||
|
const stats = data.stats ?? {};
|
||||||
|
const passed = stats.expected ?? 0;
|
||||||
|
const failed = stats.unexpected ?? 0;
|
||||||
|
const flaky = stats.flaky ?? 0;
|
||||||
|
const skipped = stats.skipped ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: passed + failed + flaky + skipped,
|
||||||
|
passed,
|
||||||
|
failed,
|
||||||
|
skipped,
|
||||||
|
flaky,
|
||||||
|
duration: stats.duration ?? 0,
|
||||||
|
success: failed === 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCoverageSummary(data) {
|
||||||
|
const total = data.total ?? {};
|
||||||
|
|
||||||
|
const files = [];
|
||||||
|
for (const [filePath, entry] of Object.entries(data)) {
|
||||||
|
if (filePath === "total") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
files.push({
|
||||||
|
file: filePath.replace(/^.*?\/src\//, "src/"),
|
||||||
|
lines: entry.lines?.pct ?? 0,
|
||||||
|
branches: entry.branches?.pct ?? 0,
|
||||||
|
functions: entry.functions?.pct ?? 0,
|
||||||
|
statements: entry.statements?.pct ?? 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
files.sort((a, b) => a.lines - b.lines);
|
||||||
|
|
||||||
|
return {
|
||||||
|
lines: total.lines?.pct ?? 0,
|
||||||
|
branches: total.branches?.pct ?? 0,
|
||||||
|
functions: total.functions?.pct ?? 0,
|
||||||
|
statements: total.statements?.pct ?? 0,
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMarkdown(name, results, coverage) {
|
||||||
|
const statusIcon =
|
||||||
|
results.failed > 0
|
||||||
|
? "\u274c"
|
||||||
|
: results.flaky > 0
|
||||||
|
? "\u26a0\ufe0f"
|
||||||
|
: "\u2705";
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
lines.push(`### ${statusIcon} ${name}`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("| Metric | Value |");
|
||||||
|
lines.push("|--------|-------|");
|
||||||
|
lines.push(`| Total | ${results.total} |`);
|
||||||
|
lines.push(`| Passed | ${results.passed} |`);
|
||||||
|
lines.push(`| Failed | ${results.failed} |`);
|
||||||
|
lines.push(`| Skipped | ${results.skipped} |`);
|
||||||
|
if (results.flaky > 0) {
|
||||||
|
lines.push(`| Flaky | ${results.flaky} |`);
|
||||||
|
}
|
||||||
|
lines.push(`| Duration | ${formatDuration(results.duration)} |`);
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
if (coverage) {
|
||||||
|
lines.push("#### Coverage");
|
||||||
|
lines.push("");
|
||||||
|
lines.push("| Metric | Coverage |");
|
||||||
|
lines.push("|--------|----------|");
|
||||||
|
lines.push(`| Lines | ${coverage.lines}% |`);
|
||||||
|
lines.push(`| Branches | ${coverage.branches}% |`);
|
||||||
|
lines.push(`| Functions | ${coverage.functions}% |`);
|
||||||
|
lines.push(`| Statements | ${coverage.statements}% |`);
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
if (coverage.files?.length > 0) {
|
||||||
|
lines.push("<details>");
|
||||||
|
lines.push(
|
||||||
|
`<summary>File coverage (${coverage.files.length} files)</summary>`,
|
||||||
|
);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("| File | Lines | Branches | Functions |");
|
||||||
|
lines.push("|------|-------|----------|-----------|");
|
||||||
|
for (const file of coverage.files) {
|
||||||
|
lines.push(
|
||||||
|
`| ${file.file} | ${file.lines}% | ${file.branches}% | ${file.functions}% |`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
lines.push("</details>");
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARTIFACT_CONFIGS = [
|
||||||
|
{
|
||||||
|
pattern: "report-server-unit",
|
||||||
|
name: "Server Unit Tests",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: "coverage/coverage-summary.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-web-unit",
|
||||||
|
name: "Web Unit Tests",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: "coverage/coverage-summary.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-server-medium",
|
||||||
|
name: "Server Medium Tests",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results-medium.json",
|
||||||
|
coverageFile: "coverage/coverage-summary.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-cli-unit",
|
||||||
|
name: "CLI Unit Tests",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-cli-unit-win",
|
||||||
|
name: "CLI Unit Tests (Windows)",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-e2e-server-cli-",
|
||||||
|
name: "E2E Server & CLI",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-e2e-server-maintenance-",
|
||||||
|
name: "E2E Server Maintenance",
|
||||||
|
framework: "vitest",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-e2e-web-",
|
||||||
|
name: "E2E Web",
|
||||||
|
framework: "playwright",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-e2e-web-ui-",
|
||||||
|
name: "E2E Web UI",
|
||||||
|
framework: "playwright",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "report-e2e-web-maintenance-",
|
||||||
|
name: "E2E Web Maintenance",
|
||||||
|
framework: "playwright",
|
||||||
|
testFile: "test-results.json",
|
||||||
|
coverageFile: undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function getStatusIcon(results) {
|
||||||
|
if (results.failed > 0) {
|
||||||
|
return "\u274c";
|
||||||
|
}
|
||||||
|
if (results.flaky > 0) {
|
||||||
|
return "\u26a0\ufe0f";
|
||||||
|
}
|
||||||
|
return "\u2705";
|
||||||
|
}
|
||||||
|
|
||||||
|
function discoverArtifacts(artifactsDir) {
|
||||||
|
const dirs = readdirSync(artifactsDir, { withFileTypes: true })
|
||||||
|
.filter((d) => d.isDirectory())
|
||||||
|
.map((d) => d.name);
|
||||||
|
|
||||||
|
const suites = [];
|
||||||
|
|
||||||
|
for (const dir of dirs) {
|
||||||
|
const config = ARTIFACT_CONFIGS.find((c) => dir.startsWith(c.pattern));
|
||||||
|
if (!config) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffix = dir.slice(config.pattern.length);
|
||||||
|
const displayName = suffix ? `${config.name} (${suffix})` : config.name;
|
||||||
|
|
||||||
|
const testFilePath = join(artifactsDir, dir, config.testFile);
|
||||||
|
const testData = readJson(testFilePath);
|
||||||
|
if (!testData) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results =
|
||||||
|
config.framework === "playwright"
|
||||||
|
? parsePlaywrightResults(testData)
|
||||||
|
: parseVitestResults(testData);
|
||||||
|
|
||||||
|
let coverage;
|
||||||
|
if (config.coverageFile) {
|
||||||
|
const coveragePath = join(artifactsDir, dir, config.coverageFile);
|
||||||
|
const coverageData = readJson(coveragePath);
|
||||||
|
if (coverageData) {
|
||||||
|
coverage = parseCoverageSummary(coverageData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suites.push({ name: displayName, results, coverage });
|
||||||
|
}
|
||||||
|
|
||||||
|
return suites;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPrComment(suites) {
|
||||||
|
if (suites.length === 0) {
|
||||||
|
return "## Test Report\n\nNo test results found.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
const totalFailed = suites.reduce((s, r) => s + r.results.failed, 0);
|
||||||
|
const totalFlaky = suites.reduce((s, r) => s + r.results.flaky, 0);
|
||||||
|
const overallIcon =
|
||||||
|
totalFailed > 0 ? "\u274c" : totalFlaky > 0 ? "\u26a0\ufe0f" : "\u2705";
|
||||||
|
|
||||||
|
lines.push(`## ${overallIcon} Test Report`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("| Suite | Tests | Passed | Failed | Skipped | Duration |");
|
||||||
|
lines.push("|-------|------:|-------:|-------:|--------:|---------:|");
|
||||||
|
|
||||||
|
for (const suite of suites) {
|
||||||
|
const { results } = suite;
|
||||||
|
const icon = getStatusIcon(results);
|
||||||
|
const flaky = results.flaky > 0 ? ` (${results.flaky} flaky)` : "";
|
||||||
|
lines.push(
|
||||||
|
`| ${icon} ${suite.name} | ${results.total} | ${results.passed} | ${results.failed}${flaky} | ${results.skipped} | ${formatDuration(results.duration)} |`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
const suitesWithCoverage = suites.filter((s) => s.coverage);
|
||||||
|
if (suitesWithCoverage.length > 0) {
|
||||||
|
lines.push("### Coverage");
|
||||||
|
lines.push("");
|
||||||
|
lines.push("| Suite | Lines | Branches | Functions | Statements |");
|
||||||
|
lines.push("|-------|------:|---------:|----------:|-----------:|");
|
||||||
|
|
||||||
|
for (const suite of suitesWithCoverage) {
|
||||||
|
const c = suite.coverage;
|
||||||
|
lines.push(
|
||||||
|
`| ${suite.name} | ${c.lines}% | ${c.branches}% | ${c.functions}% | ${c.statements}% |`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
const allFiles = suitesWithCoverage.flatMap(
|
||||||
|
(s) =>
|
||||||
|
s.coverage.files?.map((f) => ({
|
||||||
|
...f,
|
||||||
|
suite: s.name,
|
||||||
|
})) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (allFiles.length > 0) {
|
||||||
|
lines.push("<details>");
|
||||||
|
lines.push(`<summary>File coverage (${allFiles.length} files)</summary>`);
|
||||||
|
lines.push("");
|
||||||
|
|
||||||
|
for (const suite of suitesWithCoverage) {
|
||||||
|
if (!suite.coverage.files?.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lines.push(`#### ${suite.name}`);
|
||||||
|
lines.push("");
|
||||||
|
lines.push("| File | Lines | Branches | Functions |");
|
||||||
|
lines.push("|------|------:|---------:|----------:|");
|
||||||
|
for (const file of suite.coverage.files) {
|
||||||
|
lines.push(
|
||||||
|
`| ${file.file} | ${file.lines}% | ${file.branches}% | ${file.functions}% |`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("</details>");
|
||||||
|
lines.push("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values["pr-comment"]) {
|
||||||
|
const artifactsDir = values["artifacts-dir"];
|
||||||
|
if (!artifactsDir || !existsSync(artifactsDir)) {
|
||||||
|
console.error(`Artifacts directory not found: ${artifactsDir}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const suites = discoverArtifacts(artifactsDir);
|
||||||
|
const markdown = buildPrComment(suites);
|
||||||
|
process.stdout.write(markdown);
|
||||||
|
} else {
|
||||||
|
const summaryFile = process.env.GITHUB_STEP_SUMMARY;
|
||||||
|
if (!summaryFile) {
|
||||||
|
console.error("GITHUB_STEP_SUMMARY is not set");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const testData = readJson(values.json);
|
||||||
|
if (!testData) {
|
||||||
|
const fallback = `### \u26a0\ufe0f ${values.name}\n\nNo test results found at \`${values.json}\`\n\n`;
|
||||||
|
appendFileSync(summaryFile, fallback);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results =
|
||||||
|
values.framework === "playwright"
|
||||||
|
? parsePlaywrightResults(testData)
|
||||||
|
: parseVitestResults(testData);
|
||||||
|
|
||||||
|
const coverageData = values.coverage ? readJson(values.coverage) : undefined;
|
||||||
|
const coverage = coverageData
|
||||||
|
? parseCoverageSummary(coverageData)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const markdown = buildMarkdown(values.name, results, coverage);
|
||||||
|
appendFileSync(summaryFile, markdown);
|
||||||
|
}
|
||||||
+146
-7
@@ -94,8 +94,20 @@ jobs:
|
|||||||
run: pnpm check
|
run: pnpm check
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run small tests & coverage
|
- name: Run small tests & coverage
|
||||||
run: pnpm test
|
run: pnpm test --reporter=default --reporter=json --outputFile test-results.json --coverage --coverage.reporter=text --coverage.reporter=json-summary --coverage.reporter=json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json test-results.json --name "Server Unit Tests" --framework vitest --coverage coverage/coverage-summary.json
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-server-unit
|
||||||
|
path: |
|
||||||
|
server/test-results.json
|
||||||
|
server/coverage/coverage-summary.json
|
||||||
|
retention-days: 1
|
||||||
cli-unit-tests:
|
cli-unit-tests:
|
||||||
name: Unit Test CLI
|
name: Unit Test CLI
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -141,8 +153,18 @@ jobs:
|
|||||||
run: pnpm check
|
run: pnpm check
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run unit tests & coverage
|
- name: Run unit tests & coverage
|
||||||
run: pnpm test
|
run: pnpm test --reporter=default --reporter=json --outputFile test-results.json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json test-results.json --name "CLI Unit Tests" --framework vitest
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-cli-unit
|
||||||
|
path: cli/test-results.json
|
||||||
|
retention-days: 1
|
||||||
cli-unit-tests-win:
|
cli-unit-tests-win:
|
||||||
name: Unit Test CLI (Windows)
|
name: Unit Test CLI (Windows)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -183,8 +205,18 @@ jobs:
|
|||||||
run: pnpm check
|
run: pnpm check
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run unit tests & coverage
|
- name: Run unit tests & coverage
|
||||||
run: pnpm test
|
run: pnpm test --reporter=default --reporter=json --outputFile test-results.json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json test-results.json --name "CLI Unit Tests (Windows)" --framework vitest
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-cli-unit-win
|
||||||
|
path: cli/test-results.json
|
||||||
|
retention-days: 1
|
||||||
web-lint:
|
web-lint:
|
||||||
name: Lint Web
|
name: Lint Web
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -268,8 +300,20 @@ jobs:
|
|||||||
run: pnpm check:typescript
|
run: pnpm check:typescript
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run unit tests & coverage
|
- name: Run unit tests & coverage
|
||||||
run: pnpm test
|
run: pnpm test --reporter=default --reporter=json --outputFile test-results.json --coverage --coverage.reporter=text --coverage.reporter=json-summary --coverage.reporter=json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json test-results.json --name "Web Unit Tests" --framework vitest --coverage coverage/coverage-summary.json
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-web-unit
|
||||||
|
path: |
|
||||||
|
web/test-results.json
|
||||||
|
web/coverage/coverage-summary.json
|
||||||
|
retention-days: 1
|
||||||
i18n-tests:
|
i18n-tests:
|
||||||
name: Test i18n
|
name: Test i18n
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -395,8 +439,20 @@ jobs:
|
|||||||
- name: Run pnpm install
|
- name: Run pnpm install
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
||||||
- name: Run medium tests
|
- name: Run medium tests
|
||||||
run: pnpm test:medium
|
run: pnpm test:medium --reporter=default --reporter=json --outputFile test-results-medium.json --coverage --coverage.reporter=text --coverage.reporter=json-summary --coverage.reporter=json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json test-results-medium.json --name "Server Medium Tests" --framework vitest --coverage coverage/coverage-summary.json
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-server-medium
|
||||||
|
path: |
|
||||||
|
server/test-results-medium.json
|
||||||
|
server/coverage/coverage-summary.json
|
||||||
|
retention-days: 1
|
||||||
e2e-tests-server-cli:
|
e2e-tests-server-cli:
|
||||||
name: End-to-End Tests (Server & CLI)
|
name: End-to-End Tests (Server & CLI)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -438,8 +494,18 @@ jobs:
|
|||||||
run: docker compose up -d --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run e2e tests (api & cli)
|
- name: Run e2e tests (api & cli)
|
||||||
run: docker compose --profile test run --rm e2e-runner pnpm test
|
run: docker compose --profile test run --rm e2e-runner pnpm test --reporter=default --reporter=json --outputFile playwright-report/test-results.json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json playwright-report/test-results.json --name "E2E Server & CLI Tests (${{ matrix.runner }})" --framework vitest
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-e2e-server-cli-${{ matrix.runner }}
|
||||||
|
path: e2e/playwright-report/test-results.json
|
||||||
|
retention-days: 1
|
||||||
- name: Capture Docker logs
|
- name: Capture Docker logs
|
||||||
if: always()
|
if: always()
|
||||||
run: docker compose logs --no-color > docker-compose-logs.txt
|
run: docker compose logs --no-color > docker-compose-logs.txt
|
||||||
@@ -490,8 +556,18 @@ jobs:
|
|||||||
run: docker compose up -d --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run e2e tests (maintenance)
|
- name: Run e2e tests (maintenance)
|
||||||
run: docker compose --profile test run --rm e2e-runner pnpm test:maintenance
|
run: docker compose --profile test run --rm e2e-runner pnpm test:maintenance --reporter=default --reporter=json --outputFile playwright-report/test-results.json
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json playwright-report/test-results.json --name "E2E Server Maintenance Tests (${{ matrix.runner }})" --framework vitest
|
||||||
|
- name: Upload test results
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-e2e-server-maintenance-${{ matrix.runner }}
|
||||||
|
path: e2e/playwright-report/test-results.json
|
||||||
|
retention-days: 1
|
||||||
- name: Capture Docker logs
|
- name: Capture Docker logs
|
||||||
if: always()
|
if: always()
|
||||||
run: docker compose logs --no-color > docker-compose-logs.txt
|
run: docker compose logs --no-color > docker-compose-logs.txt
|
||||||
@@ -544,6 +620,16 @@ jobs:
|
|||||||
- name: Run e2e tests (web)
|
- name: Run e2e tests (web)
|
||||||
run: docker compose --profile test run --rm e2e-runner pnpm test:web
|
run: docker compose --profile test run --rm e2e-runner pnpm test:web
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary (web)
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json playwright-report/test-results.json --name "E2E Web Tests (${{ matrix.runner }})" --framework playwright
|
||||||
|
- name: Upload test results (web)
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-e2e-web-${{ matrix.runner }}
|
||||||
|
path: e2e/playwright-report/test-results.json
|
||||||
|
retention-days: 1
|
||||||
- name: Archive e2e test (web) results
|
- name: Archive e2e test (web) results
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
@@ -602,6 +688,16 @@ jobs:
|
|||||||
- name: Run ui tests (web)
|
- name: Run ui tests (web)
|
||||||
run: docker compose --profile test run --rm e2e-runner pnpm test:web:ui
|
run: docker compose --profile test run --rm e2e-runner pnpm test:web:ui
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary (ui)
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json playwright-report/test-results.json --name "E2E Web UI Tests (${{ matrix.runner }})" --framework playwright
|
||||||
|
- name: Upload test results (ui)
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-e2e-web-ui-${{ matrix.runner }}
|
||||||
|
path: e2e/playwright-report/test-results.json
|
||||||
|
retention-days: 1
|
||||||
- name: Archive ui test (web) results
|
- name: Archive ui test (web) results
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
@@ -660,6 +756,16 @@ jobs:
|
|||||||
- name: Run maintenance tests
|
- name: Run maintenance tests
|
||||||
run: docker compose --profile test run --rm e2e-runner pnpm test:web:maintenance
|
run: docker compose --profile test run --rm e2e-runner pnpm test:web:maintenance
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Write test summary (maintenance)
|
||||||
|
if: always()
|
||||||
|
run: node ${{ github.workspace }}/.github/scripts/write-test-summary.mjs --json playwright-report/test-results.json --name "E2E Web Maintenance Tests (${{ matrix.runner }})" --framework playwright
|
||||||
|
- name: Upload test results (maintenance)
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
|
with:
|
||||||
|
name: report-e2e-web-maintenance-${{ matrix.runner }}
|
||||||
|
path: e2e/playwright-report/test-results.json
|
||||||
|
retention-days: 1
|
||||||
- name: Archive maintenance tests (web) results
|
- name: Archive maintenance tests (web) results
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
@@ -692,6 +798,39 @@ jobs:
|
|||||||
- uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4
|
- uses: immich-app/devtools/actions/success-check@68f10eb389bb02a3cf9d1156111964c549eb421b # 0.0.4
|
||||||
with:
|
with:
|
||||||
needs: ${{ toJSON(needs) }}
|
needs: ${{ toJSON(needs) }}
|
||||||
|
test-report:
|
||||||
|
name: PR Test Report
|
||||||
|
if: github.event_name == 'pull_request' && always()
|
||||||
|
needs:
|
||||||
|
- server-unit-tests
|
||||||
|
- web-unit-tests
|
||||||
|
- server-medium-tests
|
||||||
|
- cli-unit-tests
|
||||||
|
- cli-unit-tests-win
|
||||||
|
- e2e-tests-server-cli
|
||||||
|
- e2e-tests-server-maintenance
|
||||||
|
- e2e-tests-web
|
||||||
|
- e2e-tests-web-ui
|
||||||
|
- e2e-tests-web-maintenance
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
pattern: 'report-*'
|
||||||
|
path: artifacts
|
||||||
|
- name: Generate unified report
|
||||||
|
run: node .github/scripts/write-test-summary.mjs --pr-comment --artifacts-dir artifacts > pr-comment.md
|
||||||
|
- name: Post PR comment
|
||||||
|
uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3.0.2
|
||||||
|
with:
|
||||||
|
header: test-report
|
||||||
|
path: pr-comment.md
|
||||||
mobile-unit-tests:
|
mobile-unit-tests:
|
||||||
name: Unit Test Mobile
|
name: Unit Test Mobile
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./test-assets:/app/e2e/test-assets
|
- ./test-assets:/app/e2e/test-assets
|
||||||
- ./playwright-report:/app/e2e/playwright-report
|
- ./playwright-report:/app/e2e/playwright-report
|
||||||
|
- ./blob-report:/app/e2e/blob-report
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
depends_on:
|
depends_on:
|
||||||
immich-server:
|
immich-server:
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ const config: PlaywrightTestConfig = {
|
|||||||
fullyParallel: false,
|
fullyParallel: false,
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 4 : 0,
|
retries: process.env.CI ? 4 : 0,
|
||||||
reporter: 'html',
|
reporter: [
|
||||||
|
['html'],
|
||||||
|
['json', { outputFile: 'playwright-report/test-results.json' }],
|
||||||
|
...(process.env.CI ? [['blob', { outputDir: 'blob-report' }] as const] : []),
|
||||||
|
],
|
||||||
use: {
|
use: {
|
||||||
baseURL: playwriteBaseUrl,
|
baseURL: playwriteBaseUrl,
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
|
|||||||
Reference in New Issue
Block a user