Skip to content

Commit 617f15d

Browse files
committed
Stabilize editor toolbar coverage flow
1 parent 20b5e90 commit 617f15d

6 files changed

Lines changed: 139 additions & 58 deletions

File tree

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ env:
2323

2424
jobs:
2525
build_supporting_suites:
26-
name: Restore + Build Supporting Suites
26+
name: Build Supporting Tests
2727
runs-on: macos-latest
2828

2929
permissions:
@@ -48,7 +48,7 @@ jobs:
4848
run: dotnet build "$SOLUTION_FILE" -warnaserror
4949

5050
test_supporting_suites:
51-
name: Run Supporting Suites
51+
name: Supporting Tests
5252
needs: build_supporting_suites
5353
runs-on: macos-latest
5454

@@ -87,7 +87,7 @@ jobs:
8787
if-no-files-found: ignore
8888

8989
build_browser_suites:
90-
name: Restore + Build Browser Suites
90+
name: Build Browser Tests
9191
runs-on: macos-latest
9292

9393
permissions:
@@ -111,8 +111,8 @@ jobs:
111111
- name: Build solution
112112
run: dotnet build "$SOLUTION_FILE" -warnaserror
113113

114-
test_browser_suites:
115-
name: Run Browser Suite (${{ matrix.suite.name }})
114+
browser_tests:
115+
name: ${{ matrix.suite.name }}
116116
needs: build_browser_suites
117117
runs-on: macos-latest
118118
strategy:
@@ -185,7 +185,7 @@ jobs:
185185
name: Resolve Release Version
186186
needs:
187187
- test_supporting_suites
188-
- test_browser_suites
188+
- browser_tests
189189
runs-on: macos-latest
190190
env:
191191
APP_PROJECT: src/PrompterOne.Web/PrompterOne.Web.csproj

.github/workflows/pr-validation.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ env:
2121

2222
jobs:
2323
build_supporting_suites:
24-
name: Restore + Build Supporting Suites
24+
name: Build Supporting Tests
2525
runs-on: macos-latest
2626

2727
steps:
@@ -43,7 +43,7 @@ jobs:
4343
run: dotnet build "$SOLUTION_FILE" -warnaserror
4444

4545
test_supporting_suites:
46-
name: Run Supporting Suites
46+
name: Supporting Tests
4747
needs: build_supporting_suites
4848
runs-on: macos-latest
4949

@@ -79,7 +79,7 @@ jobs:
7979
if-no-files-found: ignore
8080

8181
build_browser_suites:
82-
name: Restore + Build Browser Suites
82+
name: Build Browser Tests
8383
runs-on: macos-latest
8484

8585
steps:
@@ -100,8 +100,8 @@ jobs:
100100
- name: Build solution
101101
run: dotnet build "$SOLUTION_FILE" -warnaserror
102102

103-
test_browser_suites:
104-
name: Run Browser Suite (${{ matrix.suite.name }})
103+
browser_tests:
104+
name: ${{ matrix.suite.name }}
105105
needs: build_browser_suites
106106
runs-on: macos-latest
107107
strategy:

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ Browser test execution rules:
202202
- Do not run any `PrompterOne.Web.UITests*` project in parallel with another `dotnet build` or `dotnet test` command on the same local machine context.
203203
- In GitHub Actions, run the browser suite family in dedicated macOS jobs or matrix entries and keep supporting suites in separate jobs so CI can parallelize work without Linux x64 browser-runner contention stretching release validation.
204204
- GitHub Actions pipelines must expose explicit staged jobs with readable names such as restore, build, supporting tests, browser tests, release publish, and deploy; vague single-job `validate` graphs are not acceptable when the user needs to see pipeline phases clearly in the Actions UI.
205+
- GitHub Actions browser-test matrix jobs must use the suite name itself as the visible job name, such as `Shell`, `Studio`, `Editor`, or `Reader`; do not wrap it in long labels like `Run Browser Suite (...)` because GitHub truncates the useful part.
205206
- When monitoring long-running GitHub Actions jobs from the terminal, poll with coarse waits of roughly `3-5` minutes between checks; frequent short-interval polling is noise and does not help on multi-minute browser suites.
206207
- Browser acceptance tests must stay on the production-shaped runtime path; do not add or keep `?wasm-debug=1` or similar debug-query scenarios in automated acceptance coverage unless the user explicitly asks for that path.
207208
- Browser acceptance tests must be isolation-safe under in-suite parallelism: each scenario must create and own its own script, storage, and browser context state instead of mutating shared documents, shared pages, or leftover browser storage from another test.

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@ public enum EditorScenarioSelectionMode
1212
public sealed record EditorCommandScenario(
1313
string TestId,
1414
string? MenuTriggerTestId,
15+
string? MenuPanelTestId,
1516
EditorCommandRequest Command,
16-
EditorScenarioSelectionMode SelectionMode);
17+
EditorScenarioSelectionMode SelectionMode)
18+
{
19+
public override string ToString() => TestId;
20+
}
1721

1822
public sealed record EditorMenuScenario(
1923
string TriggerTestId,
20-
string PanelTestId);
24+
string PanelTestId)
25+
{
26+
public override string ToString() => TriggerTestId;
27+
}
2128

2229
internal static class EditorToolbarCoverageScenarios
2330
{
@@ -54,6 +61,7 @@ private static IEnumerable<EditorCommandScenario> BuildFloatingDirectCommandScen
5461
.Select(action => new EditorCommandScenario(
5562
action.TestId!,
5663
null,
64+
null,
5765
action.Command!,
5866
GetFloatingSelectionMode(action.Command!)));
5967

@@ -65,6 +73,7 @@ private static IEnumerable<EditorCommandScenario> BuildFloatingMenuCommandScenar
6573
.Select(action => new EditorCommandScenario(
6674
action.TestId!,
6775
menu.TriggerTestId,
76+
menu.PanelTestId,
6877
action.Command!,
6978
GetFloatingSelectionMode(action.Command!)))));
7079

@@ -74,7 +83,7 @@ private static IReadOnlyList<EditorCommandScenario> BuildToolbarCommandScenarios
7483

7584
foreach (var section in EditorToolbarCatalog.Sections)
7685
{
77-
scenarios.AddRange(BuildSectionCommandScenarios(section.MainActions, null));
86+
scenarios.AddRange(BuildSectionCommandScenarios(section.MainActions, null, null));
7887

7988
if (!string.IsNullOrWhiteSpace(section.MainActions.FirstOrDefault(action => action.ActionType == EditorToolbarActionType.ToggleMenu)?.TestId))
8089
{
@@ -84,7 +93,7 @@ private static IReadOnlyList<EditorCommandScenario> BuildToolbarCommandScenarios
8493

8594
foreach (var group in section.DropdownGroups)
8695
{
87-
scenarios.AddRange(BuildSectionCommandScenarios(group.Actions, menuTriggerTestId));
96+
scenarios.AddRange(BuildSectionCommandScenarios(group.Actions, menuTriggerTestId, section.DropdownTestId));
8897
}
8998
}
9099
}
@@ -94,12 +103,14 @@ private static IReadOnlyList<EditorCommandScenario> BuildToolbarCommandScenarios
94103

95104
private static IEnumerable<EditorCommandScenario> BuildSectionCommandScenarios(
96105
IReadOnlyList<EditorToolbarActionDescriptor> actions,
97-
string? menuTriggerTestId) =>
106+
string? menuTriggerTestId,
107+
string? menuPanelTestId) =>
98108
actions
99109
.Where(action => action.ActionType == EditorToolbarActionType.Command && action.Command is not null && !string.IsNullOrWhiteSpace(action.TestId))
100110
.Select(action => new EditorCommandScenario(
101111
action.TestId!,
102112
menuTriggerTestId,
113+
menuPanelTestId,
103114
action.Command!,
104115
GetSelectionMode(action.Command!)));
105116

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

Lines changed: 107 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,89 +21,155 @@ public sealed class EditorToolbarCoverageTests(StandaloneAppFixture fixture)
2121
public static IEnumerable<EditorCommandScenario> FloatingCommandScenarios => EditorToolbarCoverageScenarios.FloatingCommandScenarios;
2222

2323
[Test]
24-
[MethodDataSource(nameof(MenuScenarios))]
25-
public async Task EditorToolbar_MenuTrigger_ExposesExpectedBehavior(EditorMenuScenario scenario)
24+
public async Task EditorToolbar_MenuTriggers_ExposeExpectedBehavior()
2625
{
2726
await UiPageScenarioDriver.RunWithIsolatedPageRetryAsync(
2827
_fixture,
2928
async page =>
3029
{
3130
await OpenEditorAsync(page);
32-
await page.GetByTestId(scenario.TriggerTestId).ClickAsync();
33-
await Expect(page.GetByTestId(scenario.PanelTestId))
34-
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.FastVisibleTimeoutMs });
31+
32+
foreach (var scenario in MenuScenarios)
33+
{
34+
await RunCoverageStepAsync(
35+
scenario.TriggerTestId,
36+
async () =>
37+
{
38+
await UiInteractionDriver.ClickAndWaitForVisibleAsync(
39+
page.GetByTestId(scenario.TriggerTestId),
40+
page.GetByTestId(scenario.PanelTestId));
41+
await page.Keyboard.PressAsync("Escape");
42+
});
43+
}
3544
},
36-
$"Toolbar menu coverage failed for scenario '{scenario.TriggerTestId}'.");
45+
"Toolbar menu coverage failed.");
3746
}
3847

3948
[Test]
40-
[MethodDataSource(nameof(FloatingMenuScenarios))]
41-
public async Task EditorToolbar_FloatingMenuTrigger_ExposesExpectedBehavior(EditorMenuScenario scenario)
49+
public async Task EditorToolbar_FloatingMenuTriggers_ExposeExpectedBehavior()
4250
{
4351
await UiPageScenarioDriver.RunWithIsolatedPageRetryAsync(
4452
_fixture,
4553
async page =>
4654
{
4755
await OpenEditorAsync(page);
48-
await SetSourceTextAndSelectPhraseAsync(page, BrowserTestSource.AlphaSource, BrowserTestSource.AlphaToken);
49-
await Expect(page.GetByTestId(UiTestIds.Editor.FloatingBar))
50-
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.FastVisibleTimeoutMs });
51-
await page.GetByTestId(scenario.TriggerTestId).ClickAsync();
52-
await Expect(page.GetByTestId(scenario.PanelTestId))
53-
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.FastVisibleTimeoutMs });
56+
57+
foreach (var scenario in FloatingMenuScenarios)
58+
{
59+
await RunCoverageStepAsync(
60+
scenario.TriggerTestId,
61+
async () =>
62+
{
63+
await SetSourceTextAndSelectPhraseAsync(page, BrowserTestSource.AlphaSource, BrowserTestSource.AlphaToken);
64+
await Expect(page.GetByTestId(UiTestIds.Editor.FloatingBar))
65+
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.FastVisibleTimeoutMs });
66+
await UiInteractionDriver.ClickAndWaitForVisibleAsync(
67+
page.GetByTestId(scenario.TriggerTestId),
68+
page.GetByTestId(scenario.PanelTestId),
69+
noWaitAfter: true);
70+
await page.Keyboard.PressAsync("Escape");
71+
});
72+
}
5473
},
55-
$"Floating toolbar menu coverage failed for scenario '{scenario.TriggerTestId}'.");
74+
"Floating toolbar menu coverage failed.");
5675
}
5776

5877
[Test]
59-
[MethodDataSource(nameof(ToolbarCommandScenarios))]
60-
public async Task EditorToolbar_CommandButton_MutatesSource(EditorCommandScenario scenario)
78+
public async Task EditorToolbar_CommandButtons_MutateSource()
6179
{
6280
await UiPageScenarioDriver.RunWithIsolatedPageRetryAsync(
6381
_fixture,
6482
async page =>
6583
{
6684
await OpenEditorAsync(page);
67-
await PrepareScenarioAsync(page, scenario);
68-
if (!string.IsNullOrWhiteSpace(scenario.MenuTriggerTestId))
85+
86+
foreach (var scenario in ToolbarCommandScenarios)
6987
{
70-
await page.GetByTestId(scenario.MenuTriggerTestId).ClickAsync();
88+
await RunCoverageStepAsync(
89+
scenario.TestId,
90+
async () =>
91+
{
92+
await PrepareScenarioAsync(page, scenario);
93+
if (!string.IsNullOrWhiteSpace(scenario.MenuTriggerTestId))
94+
{
95+
await OpenScenarioMenuAsync(page, scenario);
96+
}
97+
98+
var beforeValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
99+
await UiInteractionDriver.ClickAndContinueAsync(page.GetByTestId(scenario.TestId));
100+
var afterValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
101+
102+
await AssertCommandMutation(scenario, beforeValue, afterValue);
103+
});
71104
}
72-
73-
var beforeValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
74-
await page.GetByTestId(scenario.TestId).ClickAsync();
75-
var afterValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
76-
77-
await AssertCommandMutation(scenario, beforeValue, afterValue);
78105
},
79-
$"Toolbar command coverage failed for scenario '{scenario.TestId}'.");
106+
"Toolbar command coverage failed.");
80107
}
81108

82109
[Test]
83-
[MethodDataSource(nameof(FloatingCommandScenarios))]
84-
public async Task EditorToolbar_FloatingCommandButton_MutatesSource(EditorCommandScenario scenario)
110+
public async Task EditorToolbar_FloatingCommandButtons_MutateSource()
85111
{
86112
await UiPageScenarioDriver.RunWithIsolatedPageRetryAsync(
87113
_fixture,
88114
async page =>
89115
{
90116
await OpenEditorAsync(page);
91-
await PrepareScenarioAsync(page, scenario);
92-
await Expect(page.GetByTestId(UiTestIds.Editor.FloatingBar))
93-
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.FastVisibleTimeoutMs });
94-
await page.WaitForTimeoutAsync(BrowserTestConstants.Timing.FloatingToolbarSettleDelayMs);
95-
if (!string.IsNullOrWhiteSpace(scenario.MenuTriggerTestId))
117+
118+
foreach (var scenario in FloatingCommandScenarios)
96119
{
97-
await page.GetByTestId(scenario.MenuTriggerTestId).ClickAsync();
120+
await RunCoverageStepAsync(
121+
scenario.TestId,
122+
async () =>
123+
{
124+
await PrepareScenarioAsync(page, scenario);
125+
await Expect(page.GetByTestId(UiTestIds.Editor.FloatingBar))
126+
.ToBeVisibleAsync(new() { Timeout = BrowserTestConstants.Timing.FastVisibleTimeoutMs });
127+
if (!string.IsNullOrWhiteSpace(scenario.MenuTriggerTestId))
128+
{
129+
await OpenScenarioMenuAsync(page, scenario, noWaitAfter: true);
130+
}
131+
132+
var beforeValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
133+
await UiInteractionDriver.ClickAndContinueAsync(page.GetByTestId(scenario.TestId), noWaitAfter: true);
134+
var afterValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
135+
136+
await AssertCommandMutation(scenario, beforeValue, afterValue);
137+
});
98138
}
139+
},
140+
"Floating toolbar coverage failed.");
141+
}
142+
143+
private static async Task RunCoverageStepAsync(string scenarioId, Func<Task> step)
144+
{
145+
try
146+
{
147+
await step();
148+
}
149+
catch (Exception exception)
150+
{
151+
throw new InvalidOperationException($"Editor toolbar coverage step failed for '{scenarioId}'.", exception);
152+
}
153+
}
99154

100-
var beforeValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
101-
await page.GetByTestId(scenario.TestId).ClickAsync();
102-
var afterValue = await EditorMonacoDriver.SourceInput(page).InputValueAsync();
155+
private static async Task OpenScenarioMenuAsync(IPage page, EditorCommandScenario scenario, bool noWaitAfter = false)
156+
{
157+
if (string.IsNullOrWhiteSpace(scenario.MenuTriggerTestId))
158+
{
159+
return;
160+
}
103161

104-
await AssertCommandMutation(scenario, beforeValue, afterValue);
105-
},
106-
$"Floating toolbar coverage failed for scenario '{scenario.TestId}'.");
162+
var trigger = page.GetByTestId(scenario.MenuTriggerTestId);
163+
if (string.IsNullOrWhiteSpace(scenario.MenuPanelTestId))
164+
{
165+
await UiInteractionDriver.ClickAndContinueAsync(trigger, noWaitAfter);
166+
return;
167+
}
168+
169+
await UiInteractionDriver.ClickAndWaitForVisibleAsync(
170+
trigger,
171+
page.GetByTestId(scenario.MenuPanelTestId),
172+
noWaitAfter);
107173
}
108174

109175
private static async Task AssertCommandMutation(EditorCommandScenario scenario, string beforeValue, string afterValue)

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,8 @@ private sealed class DropdownHitTestResult
122122
public sealed record DropdownPaintScenario(
123123
int ScenarioIndex,
124124
string TriggerTestId,
125-
string PanelTestId);
125+
string PanelTestId)
126+
{
127+
public override string ToString() => TriggerTestId;
128+
}
126129
}

0 commit comments

Comments
 (0)