Skip to content

Commit a7cc9d5

Browse files
committed
Publish coverage reports in CI
1 parent f8caff9 commit a7cc9d5

10 files changed

Lines changed: 127 additions & 20 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Publish Coverage Report
2+
description: Generate and publish a human-readable coverage report from Cobertura files.
3+
4+
inputs:
5+
artifact-name:
6+
description: Name for the uploaded coverage report artifact.
7+
required: true
8+
reports:
9+
description: Semicolon-separated Cobertura report globs.
10+
required: true
11+
targetdir:
12+
description: Directory where ReportGenerator should write the generated report.
13+
required: true
14+
title:
15+
description: Title shown in the generated report and GitHub Actions summary.
16+
required: true
17+
18+
runs:
19+
using: composite
20+
steps:
21+
- name: Generate coverage report
22+
uses: danielpalme/ReportGenerator-GitHub-Action@5.5.4
23+
with:
24+
reports: ${{ inputs.reports }}
25+
targetdir: ${{ inputs.targetdir }}
26+
reporttypes: HtmlInline;MarkdownSummaryGithub;Cobertura
27+
title: ${{ inputs.title }}
28+
tag: ${{ github.run_number }}_${{ github.run_id }}
29+
30+
- name: Upload coverage report artifact
31+
uses: actions/upload-artifact@v7
32+
with:
33+
name: ${{ inputs.artifact-name }}
34+
path: ${{ inputs.targetdir }}
35+
if-no-files-found: error
36+
37+
- name: Publish coverage summary
38+
shell: bash
39+
run: |
40+
summary_path="${{ inputs.targetdir }}/SummaryGithub.md"
41+
if [[ ! -f "$summary_path" ]]; then
42+
printf '## %s\n\nCoverage summary was not generated.\n' "${{ inputs.title }}" >> "$GITHUB_STEP_SUMMARY"
43+
exit 1
44+
fi
45+
46+
printf '## %s\n\n' "${{ inputs.title }}" >> "$GITHUB_STEP_SUMMARY"
47+
cat "$summary_path" >> "$GITHUB_STEP_SUMMARY"

.github/workflows/deploy-github-pages.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ jobs:
8686
tests/PrompterOne.Web.Tests/bin/**/TestResults/**
8787
if-no-files-found: ignore
8888

89+
- name: Publish supporting suite coverage report
90+
if: always() && hashFiles('tests/PrompterOne.Core.Tests/bin/**/TestResults/*.cobertura.xml', 'tests/PrompterOne.Web.Tests/bin/**/TestResults/*.cobertura.xml') != ''
91+
uses: ./.github/actions/publish-coverage-report
92+
with:
93+
artifact-name: coverage-report-release-pipeline-supporting
94+
reports: tests/PrompterOne.Core.Tests/bin/**/TestResults/*.cobertura.xml;tests/PrompterOne.Web.Tests/bin/**/TestResults/*.cobertura.xml
95+
targetdir: coverage-report/supporting
96+
title: Release Pipeline Supporting Coverage
97+
8998
build_browser_suites:
9099
name: Build Browser Tests
91100
runs-on: macos-latest
@@ -174,6 +183,15 @@ jobs:
174183
path: tests/PrompterOne.Web.UITests.${{ matrix.suite.name }}/bin/**/TestResults/**
175184
if-no-files-found: ignore
176185

186+
- name: Publish browser suite coverage report
187+
if: always() && hashFiles(format('tests/PrompterOne.Web.UITests.{0}/bin/**/TestResults/*.cobertura.xml', matrix.suite.name)) != ''
188+
uses: ./.github/actions/publish-coverage-report
189+
with:
190+
artifact-name: coverage-report-release-pipeline-${{ matrix.suite.artifact }}
191+
reports: tests/PrompterOne.Web.UITests.${{ matrix.suite.name }}/bin/**/TestResults/*.cobertura.xml
192+
targetdir: coverage-report/${{ matrix.suite.artifact }}
193+
title: Release Pipeline ${{ matrix.suite.name }} Coverage
194+
177195
- name: Upload Playwright artifacts
178196
if: always()
179197
uses: actions/upload-artifact@v7

.github/workflows/pr-validation.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ jobs:
7878
tests/PrompterOne.Web.Tests/bin/**/TestResults/**
7979
if-no-files-found: ignore
8080

81+
- name: Publish supporting suite coverage report
82+
if: always() && hashFiles('tests/PrompterOne.Core.Tests/bin/**/TestResults/*.cobertura.xml', 'tests/PrompterOne.Web.Tests/bin/**/TestResults/*.cobertura.xml') != ''
83+
uses: ./.github/actions/publish-coverage-report
84+
with:
85+
artifact-name: coverage-report-pr-validation-supporting
86+
reports: tests/PrompterOne.Core.Tests/bin/**/TestResults/*.cobertura.xml;tests/PrompterOne.Web.Tests/bin/**/TestResults/*.cobertura.xml
87+
targetdir: coverage-report/supporting
88+
title: PR Validation Supporting Coverage
89+
8190
build_browser_suites:
8291
name: Build Browser Tests
8392
runs-on: macos-latest
@@ -160,6 +169,15 @@ jobs:
160169
path: tests/PrompterOne.Web.UITests.${{ matrix.suite.name }}/bin/**/TestResults/**
161170
if-no-files-found: ignore
162171

172+
- name: Publish browser suite coverage report
173+
if: always() && hashFiles(format('tests/PrompterOne.Web.UITests.{0}/bin/**/TestResults/*.cobertura.xml', matrix.suite.name)) != ''
174+
uses: ./.github/actions/publish-coverage-report
175+
with:
176+
artifact-name: coverage-report-pr-validation-${{ matrix.suite.artifact }}
177+
reports: tests/PrompterOne.Web.UITests.${{ matrix.suite.name }}/bin/**/TestResults/*.cobertura.xml
178+
targetdir: coverage-report/${{ matrix.suite.artifact }}
179+
title: PR Validation ${{ matrix.suite.name }} Coverage
180+
163181
- name: Upload Playwright artifacts
164182
if: always()
165183
uses: actions/upload-artifact@v7

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Rule format:
106106
- When a CI test job fails, times out, or is cancelled, the workflow summary must still state which tests failed or that the run ended before per-test failure data was available; do not leave browser-suite failures represented only by generic job annotations.
107107
- Do not add or raise CI timeout settings as a fix for browser-suite instability or slow runs; fix the underlying failure path cleanly instead of masking it with workflow or step timeouts.
108108
- For TUnit reporting in GitHub Actions, prefer TUnit's built-in HTML report and the documented `actions/github-script` runtime exposure step over repo-local custom summary scripts or ad-hoc log parsers.
109-
- GitHub Actions test jobs must use the repo `tests/dotnet-test-progress.rsp` response file and enable TUnit coverage for every runnable test suite so CI measures the same detailed, non-ANSI test output and coverage signal expected locally.
109+
- GitHub Actions test jobs must use the repo `tests/dotnet-test-progress.rsp` response file and enable TUnit coverage for every runnable test suite so CI measures the same minimal, non-ANSI test output and coverage signal expected locally; do not configure detailed per-test `Standard output` or empty `Error output` blocks as the default log format.
110110
- Public web hosting is split by role: the standalone PrompterOne app in this repo must publish on `app.prompter.one`, while the marketing landing site for `prompter.one` lives in the separate `PrompterOne-LandingPage` repository.
111111
- Runtime telemetry providers such as Google Analytics, Clarity, and Sentry must not be described as connected or working unless the real production path is verified with actual outbound delivery or loaded vendor SDKs; local harness snapshots, init flags, or stubbed globals are not sufficient proof.
112112
- Runtime telemetry readiness must be proven against Release-built app artifacts in CI; do not sign off GA, Clarity, or Sentry from Debug-only local runs when the shipped Release pipeline has not validated that path.
@@ -173,7 +173,7 @@ Rule format:
173173
For this `.NET` repo:
174174

175175
- all automated test projects run on `TUnit` and native `Microsoft.Testing.Platform`
176-
- use `@./tests/dotnet-test-progress.rsp` on repo test commands so `dotnet test` emits detailed, non-ANSI per-test progress logs consistently in terminal and CI text logs
176+
- use `@./tests/dotnet-test-progress.rsp` on repo test commands so `dotnet test` emits minimal, non-ANSI progress logs consistently in terminal and CI text logs without flooding every test with `Standard output` or empty `Error output` sections
177177
- `format` is direct `dotnet format`, not `--verify-no-changes` and not a wrapper
178178
- coverage uses TUnit's native `--coverage` support
179179
- `LangVersion` is not pinned; use the SDK default unless the repo intentionally changes it later

tests/PrompterOne.Web.UITests.Editor/Editor/EditorInteractionTests.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,7 @@ public async Task EditorScreen_DoesNotRenderLegacyStructureInspectorPanel()
288288

289289
try
290290
{
291-
await EditorIsolatedDraftDriver.CreateSeededDraftAsync(
292-
page,
293-
BrowserTestConstants.Scripts.QuantumId,
294-
waitForPersistedRoute: false);
291+
await EditorIsolatedDraftDriver.CreateSeededDraftAsync(page, BrowserTestConstants.Scripts.QuantumId);
295292
await Expect(page.GetByTestId(UiTestIds.Editor.ActiveSegmentName)).ToHaveCountAsync(0);
296293
await Expect(page.GetByTestId(UiTestIds.Editor.ActiveBlockName)).ToHaveCountAsync(0);
297294
await Expect(page.GetByTestId(UiTestIds.Editor.SegmentNavigation(0))).ToContainTextAsync("Introduction");

tests/PrompterOne.Web.UITests.Editor/Editor/EditorScriptGraphViewTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ await Expect(page.GetByTestId(UiTestIds.Editor.GraphSemanticStatus))
180180
await Assert.That(graphKinds.Contains("Literal")).IsFalse();
181181
await Assert.That(graphKinds.Contains("Uri")).IsFalse();
182182

183-
await page.GetByTestId(UiTestIds.Editor.GraphTokenizerAnalyze).ClickAsync();
183+
await UiInteractionDriver.ClickAndContinueAsync(
184+
page.GetByTestId(UiTestIds.Editor.GraphTokenizerAnalyze),
185+
noWaitAfter: true);
184186
await Expect(page.GetByTestId(UiTestIds.Editor.GraphSemanticStatus))
185187
.ToHaveAttributeAsync(
186188
BrowserTestConstants.Editor.GraphSemanticStatusAttributeName,

tests/PrompterOne.Web.UITests.Reader/Teleprompter/TeleprompterReadingChromeIntensityTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ await Assert.That(progressFillColors.Start.A <= BrowserTestConstants.Teleprompte
7979
});
8080

8181
private static bool HasMaximumChannels(CssColor color, double maximum) =>
82-
color.R <= maximum && color.G <= maximum && color.B <= maximum;
82+
EffectiveChannel(color.R, color.A) <= maximum &&
83+
EffectiveChannel(color.G, color.A) <= maximum &&
84+
EffectiveChannel(color.B, color.A) <= maximum;
85+
86+
private static double EffectiveChannel(double channel, double alpha) =>
87+
channel * alpha;
8388

8489
private static Task ClearChromeHoverAsync(IPage page) =>
8590
page.Mouse.MoveAsync(

tests/PrompterOne.Web.UITests/Support/BrowserErrorCollector.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace PrompterOne.Web.UITests;
44

55
internal sealed class BrowserErrorCollector
66
{
7+
private readonly object _gate = new();
78
private readonly List<string> _consoleErrors = [];
89
private readonly List<string> _pageErrors = [];
910

@@ -21,24 +22,45 @@ public static BrowserErrorCollector Attach(IPage page)
2122

2223
public async Task AssertNoCriticalUiErrorsAsync()
2324
{
24-
await Assert.That(_pageErrors).DoesNotContain(IsCriticalUiError);
25-
await Assert.That(_consoleErrors).DoesNotContain(IsCriticalUiError);
25+
await Assert.That(Snapshot(_pageErrors)).DoesNotContain(IsCriticalUiError);
26+
await Assert.That(Snapshot(_consoleErrors)).DoesNotContain(IsCriticalUiError);
2627
}
2728

2829
public string Describe() =>
2930
string.Join(
3031
Environment.NewLine,
31-
_consoleErrors
32-
.Concat(_pageErrors)
33-
.DefaultIfEmpty("No captured browser console or page errors."));
32+
Snapshot(_consoleErrors)
33+
.Where(IsActionableConsoleDiagnostic)
34+
.Concat(Snapshot(_pageErrors).Select(message => $"pageerror: {message}"))
35+
.DefaultIfEmpty("No captured critical browser console or page errors."));
3436

3537
private void OnConsoleMessage(object? sender, IConsoleMessage message)
3638
{
37-
_consoleErrors.Add($"{message.Type}: {message.Text}");
39+
lock (_gate)
40+
{
41+
_consoleErrors.Add($"{message.Type}: {message.Text}");
42+
}
3843
}
3944

40-
private void OnPageError(object? sender, string message) =>
41-
_pageErrors.Add(message);
45+
private void OnPageError(object? sender, string message)
46+
{
47+
lock (_gate)
48+
{
49+
_pageErrors.Add(message);
50+
}
51+
}
52+
53+
private string[] Snapshot(List<string> messages)
54+
{
55+
lock (_gate)
56+
{
57+
return [.. messages];
58+
}
59+
}
60+
61+
private static bool IsActionableConsoleDiagnostic(string message) =>
62+
message.StartsWith("error:", StringComparison.OrdinalIgnoreCase) ||
63+
IsCriticalUiError(message);
4264

4365
private static bool IsCriticalUiError(string message) =>
4466
message.Contains(BrowserTestConstants.RapidInput.UnhandledUiExceptionFragment, StringComparison.Ordinal) ||

tests/PrompterOne.Web.UITests/Support/PlaybackRouteDriver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ internal static async Task WaitForLearnReadyAsync(IPage page, string route)
3030
await BrowserRouteDriver.WaitForRouteAsync(page, route);
3131
await Expect(page.GetByTestId(UiTestIds.Learn.Page)).ToBeVisibleAsync();
3232
await Expect(page.GetByTestId(UiTestIds.Learn.Display)).ToBeVisibleAsync();
33+
await Expect(page.GetByTestId(UiTestIds.Learn.Display))
34+
.ToHaveAttributeAsync(LearnLayoutReadyAttributeName, TrueValue);
3335
await Expect(page.GetByTestId(UiTestIds.Learn.Word)).ToBeVisibleAsync();
3436
await Expect(page.GetByTestId(UiTestIds.Learn.ProgressLabel)).ToBeVisibleAsync();
3537
await Expect(page.GetByTestId(UiTestIds.Learn.PlayToggle)).ToBeVisibleAsync();
3638
await Expect(page.GetByTestId(UiTestIds.Learn.StepForward)).ToBeVisibleAsync();
3739
await Expect(page.GetByTestId(UiTestIds.Learn.StepForwardLarge)).ToBeVisibleAsync();
38-
await Expect(page.GetByTestId(UiTestIds.Learn.Display))
39-
.ToHaveAttributeAsync(LearnLayoutReadyAttributeName, TrueValue);
4040
}
4141

4242
internal static async Task WaitForTeleprompterReadyAsync(

tests/dotnet-test-progress.rsp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
--output
2-
detailed
31
--no-ansi

0 commit comments

Comments
 (0)