Skip to content

Commit 91e8888

Browse files
committed
Propagate performance budgets to Page and tests
Add page-level performance budget support and ensure test adapters set/clear it to work around AsyncLocal flow limitations. Introduce Page.ActivePerformanceBudget and PerformanceBudgetContext.SetBudget/ClearBudget; update MSTest, NUnit and xUnit test bases to Push resolved budgets, call SetBudget(_page, budget) when starting and ClearBudget(_page) during teardown. PageAssertions now prefers the page's active budget over the ambient context. Also enable performance collection in sample AssemblySetup and mark relevant sample tests as Integration.
1 parent 35677d7 commit 91e8888

8 files changed

Lines changed: 58 additions & 11 deletions

File tree

samples/Motus.Samples/AssemblySetup.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ public class AssemblySetup
55
{
66
[AssemblyInitialize]
77
public static async Task Initialize(TestContext _) =>
8-
await MotusTestBase.LaunchBrowserAsync(new LaunchOptions { Headless = true });
8+
await MotusTestBase.LaunchBrowserAsync(new LaunchOptions
9+
{
10+
Headless = true,
11+
Performance = new PerformanceOptions { Enable = true },
12+
});
913

1014
[AssemblyCleanup]
1115
public static async Task Cleanup() =>

samples/Motus.Samples/Tests/PerformanceTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,23 @@ public async Task MethodAttribute_OverridesClassBudget()
4040
}
4141

4242
[TestMethod]
43+
[TestCategory("Integration")]
4344
public async Task LcpBelow_IndividualMetricAssertion()
4445
{
4546
await Fixtures.SetPageContentAsync(Page, SimplePage);
4647
await Expect.That(Page).ToHaveLcpBelowAsync(5000);
4748
}
4849

4950
[TestMethod]
51+
[TestCategory("Integration")]
5052
public async Task FcpBelow_IndividualMetricAssertion()
5153
{
5254
await Fixtures.SetPageContentAsync(Page, SimplePage);
5355
await Expect.That(Page).ToHaveFcpBelowAsync(5000);
5456
}
5557

5658
[TestMethod]
59+
[TestCategory("Integration")]
5760
public async Task ClsBelow_NoLayoutShift()
5861
{
5962
await Fixtures.SetPageContentAsync(Page, SimplePage);

src/Motus.Testing.MSTest/MotusTestBase.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,18 @@ public async Task MotusTestInitialize()
8282
var methodAttr = methodInfo?.GetCustomAttribute<PerformanceBudgetAttribute>();
8383
var classAttr = GetType().GetCustomAttribute<PerformanceBudgetAttribute>();
8484
var activeAttr = methodAttr ?? classAttr;
85-
PerformanceBudgetContext.Push(activeAttr?.ToBudget());
85+
var budget = activeAttr?.ToBudget();
86+
PerformanceBudgetContext.Push(budget);
87+
PerformanceBudgetContext.SetBudget(_page, budget);
8688
}
8789

8890
[TestCleanup]
8991
public async Task MotusTestCleanup()
9092
{
93+
if (_page is not null)
94+
PerformanceBudgetContext.ClearBudget(_page);
95+
PerformanceBudgetContext.Clear();
96+
9197
if (_context is not null)
9298
{
9399
var testFailed = TestContext?.CurrentTestOutcome != UnitTestOutcome.Passed;
@@ -98,7 +104,5 @@ public async Task MotusTestCleanup()
98104
_context = null;
99105
_page = null;
100106
}
101-
102-
PerformanceBudgetContext.Clear();
103107
}
104108
}

src/Motus.Testing.NUnit/MotusTestBase.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,18 @@ public async Task SetUp()
7272
var methodAttr = methodInfo?.GetCustomAttribute<PerformanceBudgetAttribute>();
7373
var classAttr = GetType().GetCustomAttribute<PerformanceBudgetAttribute>();
7474
var activeAttr = methodAttr ?? classAttr;
75-
PerformanceBudgetContext.Push(activeAttr?.ToBudget());
75+
var budget = activeAttr?.ToBudget();
76+
PerformanceBudgetContext.Push(budget);
77+
PerformanceBudgetContext.SetBudget(_page, budget);
7678
}
7779

7880
[TearDown]
7981
public async Task TearDown()
8082
{
83+
if (_page is not null)
84+
PerformanceBudgetContext.ClearBudget(_page);
85+
PerformanceBudgetContext.Clear();
86+
8187
if (_context is not null)
8288
{
8389
var testFailed = TestContext.CurrentContext.Result.Outcome.Status == global::NUnit.Framework.Interfaces.TestStatus.Failed;
@@ -88,7 +94,5 @@ public async Task TearDown()
8894
_context = null;
8995
_page = null;
9096
}
91-
92-
PerformanceBudgetContext.Clear();
9397
}
9498
}

src/Motus.Testing.xUnit/BrowserContextFixture.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,22 @@ public async Task InitializeAsync()
4545
_page = await _context.NewPageAsync();
4646

4747
var classAttr = GetType().GetCustomAttribute<PerformanceBudgetAttribute>();
48-
PerformanceBudgetContext.Push(classAttr?.ToBudget());
48+
var budget = classAttr?.ToBudget();
49+
PerformanceBudgetContext.Push(budget);
50+
PerformanceBudgetContext.SetBudget(_page, budget);
4951
}
5052

5153
public async Task DisposeAsync()
5254
{
55+
if (_page is not null)
56+
PerformanceBudgetContext.ClearBudget(_page);
57+
PerformanceBudgetContext.Clear();
58+
5359
if (_context is not null)
5460
{
5561
await _context.CloseAsync();
5662
_context = null;
5763
_page = null;
5864
}
59-
60-
PerformanceBudgetContext.Clear();
6165
}
6266
}

src/Motus/Assertions/PageAssertions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,11 @@ public Task ToHaveInpBelowAsync(double thresholdMs, AssertionOptions? options =
150150
return (actual <= thresholdMs, $"{actual:F1}ms");
151151
}, "ToHaveInpBelow", $"< {thresholdMs}ms", options);
152152

153-
private static PerformanceBudget ResolveBudget()
153+
private PerformanceBudget ResolveBudget()
154154
{
155+
if (_page.ActivePerformanceBudget is { } pageBudget)
156+
return pageBudget;
157+
155158
if (PerformanceBudgetContext.Current is { } ambient)
156159
return ambient;
157160

src/Motus/Page/Page.Performance.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ internal sealed partial class Page
99
/// after navigation or page close. Null when the hook is disabled or no collection has run.
1010
/// </summary>
1111
internal PerformanceMetrics? LastPerformanceMetrics { get; set; }
12+
13+
/// <summary>
14+
/// The active performance budget for this page, set by test framework adapters
15+
/// from <see cref="PerformanceBudgetAttribute"/> resolution.
16+
/// </summary>
17+
internal PerformanceBudget? ActivePerformanceBudget { get; set; }
1218
}

src/Motus/Performance/PerformanceBudgetContext.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,28 @@ public static class PerformanceBudgetContext
1515
/// <summary>Sets the active budget for the current async flow.</summary>
1616
public static void Push(PerformanceBudget? budget) => s_current.Value = budget;
1717

18+
/// <summary>
19+
/// Sets the active budget directly on a page. This avoids <see cref="AsyncLocal{T}"/>
20+
/// limitations where writes inside async test initializers don't flow to the test method.
21+
/// </summary>
22+
public static void SetBudget(IPage page, PerformanceBudget? budget)
23+
{
24+
if (page is Page concrete)
25+
concrete.ActivePerformanceBudget = budget;
26+
}
27+
1828
/// <summary>Clears the active budget.</summary>
1929
public static void Clear() => s_current.Value = null;
2030

31+
/// <summary>
32+
/// Clears the budget from a page. Call this in test teardown alongside <see cref="Clear"/>.
33+
/// </summary>
34+
public static void ClearBudget(IPage page)
35+
{
36+
if (page is Page concrete)
37+
concrete.ActivePerformanceBudget = null;
38+
}
39+
2140
/// <summary>The active budget for the current async flow, or null if none set.</summary>
2241
internal static PerformanceBudget? Current => s_current.Value;
2342
}

0 commit comments

Comments
 (0)