Skip to content

Commit ab0d029

Browse files
committed
Unified SSR tests for POST and GET.
1 parent c6465d0 commit ab0d029

File tree

8 files changed

+305
-22
lines changed

8 files changed

+305
-22
lines changed

src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs

Lines changed: 155 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public void ProgrammaticNavigationToNotExistingPathReExecutesTo404(bool streamin
8787
{
8888
string streamingPath = streaming ? "-streaming" : "";
8989
Navigate($"{ServerPathBase}/reexecution/redirection-not-found-ssr{streamingPath}?navigate-programmatically=true");
90-
Assert404ReExecuted();
90+
AssertReExecutionPageRendered();
9191
}
9292

9393
[Theory]
@@ -98,7 +98,7 @@ public void LinkNavigationToNotExistingPathReExecutesTo404(bool streaming)
9898
string streamingPath = streaming ? "-streaming" : "";
9999
Navigate($"{ServerPathBase}/reexecution/redirection-not-found-ssr{streamingPath}");
100100
Browser.Click(By.Id("link-to-not-existing-page"));
101-
Assert404ReExecuted();
101+
AssertReExecutionPageRendered();
102102
}
103103

104104
[Theory]
@@ -111,40 +111,175 @@ public void BrowserNavigationToNotExistingPathReExecutesTo404(bool streaming)
111111
// will not be activated, see configuration in Startup
112112
string streamingPath = streaming ? "-streaming" : "";
113113
Navigate($"{ServerPathBase}/reexecution/not-existing-page-ssr{streamingPath}");
114-
Assert404ReExecuted();
114+
AssertReExecutionPageRendered();
115115
}
116116

117-
private void Assert404ReExecuted() =>
117+
private void AssertReExecutionPageRendered() =>
118118
Browser.Equal("Welcome On Page Re-executed After Not Found Event", () => Browser.Exists(By.Id("test-info")).Text);
119119

120+
private void AssertNotFoundPageRendered()
121+
{
122+
Browser.Equal("Welcome On Custom Not Found Page", () => Browser.FindElement(By.Id("test-info")).Text);
123+
// custom page should have a custom layout
124+
Browser.Equal("About", () => Browser.FindElement(By.Id("about-link")).Text);
125+
}
126+
127+
private void AssertUrlNotChanged(string expectedUrl) =>
128+
Browser.True(() => Browser.Url.Contains(expectedUrl), $"Expected URL to contain '{expectedUrl}', but found '{Browser.Url}'");
129+
130+
private void AssertUrlChanged(string urlPart) =>
131+
Browser.False(() => Browser.Url.Contains(urlPart), $"Expected URL not to contain '{urlPart}', but found '{Browser.Url}'");
132+
120133
[Theory]
121-
[InlineData(true)]
122-
[InlineData(false)]
123-
public void CanRenderNotFoundPage(bool streamingStarted)
134+
[InlineData(true, true)]
135+
[InlineData(true, false)]
136+
[InlineData(false, true)]
137+
[InlineData(false, false)]
138+
// When response has not started, it does not matter how we arrive the the page setting the not found status code.
139+
// We can navigate straight to the testing page, skipping the index page.
140+
public void NotFoundSetOnInitialization_ResponseNotStarted_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
124141
{
125-
string streamingPath = streamingStarted ? "-streaming" : "";
126-
Navigate($"{ServerPathBase}/set-not-found-ssr{streamingPath}?useCustomNotFoundPage=true");
142+
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
143+
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
144+
Navigate(testUrl);
127145

128-
var infoText = Browser.FindElement(By.Id("test-info")).Text;
129-
Assert.Contains("Welcome On Custom Not Found Page", infoText);
130-
// custom page should have a custom layout
131-
var aboutLink = Browser.FindElement(By.Id("about-link")).Text;
132-
Assert.Contains("About", aboutLink);
146+
if (hasCustomNotFoundPageSet)
147+
{
148+
AssertNotFoundPageRendered();
149+
}
150+
else
151+
{
152+
AssertNotFoundFragmentRendered();
153+
}
154+
AssertUrlNotChanged(testUrl);
133155
}
134156

157+
// If the response has started, it matters how we arrive the the page setting the not found status code
158+
// because we are rendering client-side. In browser-initiated navigation and form submissions, the headers
159+
// don't contain enhanced-nav and we redirect to the not found page. In link clicking navigation,
160+
// the headers contain enhanced-nav, redirection is not needed and the original url will be preserved.
161+
135162
[Theory]
136-
[InlineData(false)]
137-
[InlineData(true)]
138-
public void DoesNotReExecuteIf404WasHandled(bool streamingStarted)
163+
[InlineData(true, true)] // manual testing works, why Selenium does not?
164+
[InlineData(true, false)]
165+
[InlineData(false, true)] // manual testing works, why Selenium does not?
166+
[InlineData(false, false)]
167+
// enhanced navigation is switched off for browser navigation
168+
public void NotFoundSetOnInitialization_ResponseStarted_BrowserNavigation_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
169+
{
170+
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
171+
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr-streaming?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
172+
Navigate(testUrl);
173+
AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
174+
bool throwsException = !hasCustomNotFoundPageSet && !hasReExecutionMiddleware;
175+
if (throwsException)
176+
{
177+
AssertUrlNotChanged(testUrl);
178+
}
179+
else
180+
{
181+
AssertUrlChanged(testUrl);
182+
}
183+
}
184+
185+
private void AssertNotFoundRendered_ResponseStarted_Or_POST(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet, string testUrl)
186+
{
187+
if (hasCustomNotFoundPageSet)
188+
{
189+
AssertNotFoundPageRendered();
190+
}
191+
else if (hasReExecutionMiddleware)
192+
{
193+
AssertReExecutionPageRendered();
194+
}
195+
else
196+
{
197+
// this throws an exception logged the server
198+
AssertNotFoundContentNotRendered();
199+
}
200+
}
201+
202+
[Theory]
203+
//[InlineData(true, true)] // ActiveIssue("NotFoundPageRoute is not set in Router")
204+
[InlineData(true, false)]
205+
//[InlineData(false, true)] // ActiveIssue("NotFoundPageRoute is not set in Router")
206+
[InlineData(false, false)]
207+
// enhanced navigation is switched on for link navigation
208+
public void NotFoundSetOnInitialization_ResponseStarted_LinkNavigation_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
209+
{
210+
string testUrl = NavigateByLinkToPageTestingNotFound("Sets", hasReExecutionMiddleware, hasCustomNotFoundPageSet);
211+
AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
212+
AssertUrlNotChanged(testUrl);
213+
}
214+
215+
private string NavigateByLinkToPageTestingNotFound(string action, bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
216+
{
217+
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
218+
Navigate($"{ServerPathBase}/not-found-index?useCustomNotFoundPage={hasCustomNotFoundPageSet}");
219+
Browser.Equal("List of Not Found test pages", () => Browser.FindElement(By.Id("test-info")).Text);
220+
string reexecutionText = hasReExecutionMiddleware ? " with reexecution" : "";
221+
var link = Browser.FindElement(By.LinkText($"PageThat{action}NotFound-streaming{reexecutionText}"));
222+
var testUrl = link.GetAttribute("href");
223+
link.Click();
224+
return testUrl;
225+
}
226+
227+
[Theory]
228+
//[InlineData(true, true)] // ActiveIssue("NotFoundPageRoute is not set in Router")
229+
[InlineData(true, false)]
230+
//[InlineData(false, true)] // ActiveIssue("NotFoundPageRoute is not set in Router")
231+
[InlineData(false, false)]
232+
// NotFound triggered by POST cannot get rendered in the same batch and we rely on the client to render it
233+
// However, because it is triggered by form POST, it won't have enhanced-nav headers
234+
public void NotFoundSetOnFormSubmit_ResponseNotStarted_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
235+
{
236+
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
237+
string testUrl = $"{ServerPathBase}{reexecution}/post-not-found-ssr-streaming?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
238+
Navigate(testUrl);
239+
Browser.FindElement(By.Id("not-found-form")).FindElement(By.TagName("button")).Click();
240+
241+
AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
242+
bool throwsException = !hasCustomNotFoundPageSet && !hasReExecutionMiddleware;
243+
if (throwsException)
244+
{
245+
AssertUrlNotChanged(testUrl);
246+
}
247+
else
248+
{
249+
AssertUrlChanged(testUrl);
250+
}
251+
}
252+
253+
[Theory]
254+
//[InlineData(true, true)] // ActiveIssue("NotFoundPageRoute is not set in Router")
255+
[InlineData(true, false)]
256+
//[InlineData(false, true)] // ActiveIssue("NotFoundPageRoute is not set in Router")
257+
[InlineData(false, false)]
258+
public void NotFoundSetOnFormSubmit_ResponseStarted_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
139259
{
140-
string streamingPath = streamingStarted ? "-streaming" : "";
141-
Navigate($"{ServerPathBase}/reexecution/set-not-found-ssr{streamingPath}");
142-
AssertNotFoundFragmentRendered();
260+
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
261+
string testUrl = $"{ServerPathBase}{reexecution}/post-not-found-ssr-streaming?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
262+
Navigate(testUrl);
263+
Browser.FindElement(By.Id("not-found-form")).FindElement(By.TagName("button")).Click();
264+
265+
AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
266+
bool throwsException = !hasCustomNotFoundPageSet && !hasReExecutionMiddleware;
267+
if (throwsException)
268+
{
269+
AssertUrlNotChanged(testUrl);
270+
}
271+
else
272+
{
273+
AssertUrlChanged(testUrl);
274+
}
143275
}
144276

145277
private void AssertNotFoundFragmentRendered() =>
146278
Browser.Equal("There's nothing here", () => Browser.FindElement(By.Id("not-found-fragment")).Text);
147279

280+
private void AssertNotFoundContentNotRendered() =>
281+
Browser.Equal("Any content", () => Browser.FindElement(By.Id("test-info")).Text);
282+
148283
[Fact]
149284
public void StatusCodePagesWithReExecution()
150285
{
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@using Microsoft.AspNetCore.Components.Forms
2+
3+
@inject NavigationManager NavigationManager
4+
5+
@if (!WaitForInteractivity || RendererInfo.IsInteractive)
6+
{
7+
<PageTitle>Original page</PageTitle>
8+
9+
<form method="post" id="not-found-form" @onsubmit="HandleSubmit" @formname="PostNotFoundForm">
10+
<AntiforgeryToken />
11+
<button type="submit">Trigger NotFound</button>
12+
</form>
13+
14+
<p id="test-info">Any content</p>
15+
}
16+
17+
@code{
18+
[Parameter]
19+
public bool StartStreaming { get; set; } = false;
20+
21+
[Parameter]
22+
public bool WaitForInteractivity { get; set; } = false;
23+
24+
private async Task HandleSubmit()
25+
{
26+
if (StartStreaming)
27+
{
28+
await Task.Yield();
29+
}
30+
NavigationManager.NotFound();
31+
}
32+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
@page "/not-found-index"
2+
3+
@using Microsoft.AspNetCore.Components.Forms
4+
5+
@inject NavigationManager NavigationManager
6+
7+
<h1 id="test-info">List of Not Found test pages</h1>
8+
9+
<h2>Link navigation (enhanced nav passed in Headers)</h2>
10+
<ul>
11+
<li><a href="@PostNotFoundNoStreaming">@PostNotFoundNoStreamingText</a></li>
12+
<li><a href="@PostNotFoundNoStreamingReexecution">@PostNotFoundNoStreamingReexecutionText</a></li>
13+
14+
<li><a href="@PostNotFoundStreaming">@PostNotFoundStreamingText</a></li>
15+
<li><a href="@PostNotFoundStreamingReexecution">@PostNotFoundStreamingReexecutionText</a></li>
16+
17+
<li><a href="@SetNotFoundStreaming">@SetNotFoundNoStreamingText</a></li>
18+
<li><a href="@SetNotFoundNoStreamingReexecution">@SetNotFoundNoStreamingReexecutionText</a></li>
19+
20+
<li><a href="@SetNotFoundStreaming">@SetNotFoundStreamingText</a></li>
21+
<li><a href="@SetNotFoundStreamingReexecution">@SetNotFoundStreamingReexecutionText</a></li>
22+
</ul>
23+
24+
<h2>Programmatic navigation in a form (enhanced nav not passed in Headers)</h2>
25+
<form method="post" @onsubmit="() => NavigateTo(PostNotFoundNoStreaming)" @formname="ProgrammaticPostNotFoundNoStreamingForm">
26+
<AntiforgeryToken />
27+
<button type="submit">Programmatic navigate to @PostNotFoundNoStreamingText</button>
28+
</form>
29+
<form method="post" @onsubmit="() => NavigateTo(PostNotFoundNoStreamingReexecution)" @formname="ProgrammaticPostNotFoundNoStreamingReexecutionForm">
30+
<AntiforgeryToken />
31+
<button type="submit">Programmatic navigate to @PostNotFoundNoStreamingReexecutionText</button>
32+
</form>
33+
34+
<form method="post" @onsubmit="() => NavigateTo(PostNotFoundStreaming)" @formname="ProgrammaticPostNotFoundStreamingForm">
35+
<AntiforgeryToken />
36+
<button type="submit">Programmatic navigate to @PostNotFoundStreamingText</button>
37+
</form>
38+
<form method="post" @onsubmit="() => NavigateTo(PostNotFoundStreamingReexecution)" @formname="ProgrammaticPostNotFoundStreamingReexecutionForm">
39+
<AntiforgeryToken />
40+
<button type="submit">Programmatic navigate to @PostNotFoundStreamingReexecutionText</button>
41+
</form>
42+
43+
<form method="post" @onsubmit="() => NavigateTo(SetNotFoundNoStreaming)" @formname="ProgrammaticSetNotFoundNoStreamingForm">
44+
<AntiforgeryToken />
45+
<button type="submit">Programmatic navigate to @SetNotFoundNoStreamingText</button>
46+
</form>
47+
<form method="post" @onsubmit="() => NavigateTo(SetNotFoundNoStreamingReexecution)" @formname="ProgrammaticSetNotFoundNoStreamingReexecutionForm">
48+
<AntiforgeryToken />
49+
<button type="submit">Programmatic navigate to @SetNotFoundNoStreamingReexecutionText</button>
50+
</form>
51+
52+
<form method="post" @onsubmit="() => NavigateTo(SetNotFoundStreaming)" @formname="ProgrammaticSetNotFoundStreamingForm">
53+
<AntiforgeryToken />
54+
<button type="submit">Programmatic navigate to @SetNotFoundStreamingText</button>
55+
</form>
56+
<form method="post" @onsubmit="() => NavigateTo(SetNotFoundStreamingReexecution)" @formname="ProgrammaticSetNotFoundStreamingReexecutionForm">
57+
<AntiforgeryToken />
58+
<button type="submit">Programmatic navigate to @SetNotFoundStreamingReexecutionText</button>
59+
</form>
60+
61+
@code {
62+
[SupplyParameterFromQuery]
63+
public bool useCustomNotFoundPage { get; set; }
64+
65+
private string AppendQuery(string url) => $"{url}?useCustomNotFoundPage={useCustomNotFoundPage}";
66+
67+
private string PostNotFoundNoStreaming => AppendQuery("post-not-found-ssr");
68+
private string PostNotFoundNoStreamingReexecution => AppendQuery($"reexecution/post-not-found-ssr");
69+
private string PostNotFoundStreaming => AppendQuery("post-not-found-ssr-streaming");
70+
private string PostNotFoundStreamingReexecution => AppendQuery($"reexecution/post-not-found-ssr-streaming");
71+
private string SetNotFoundNoStreaming => AppendQuery("set-not-found-ssr");
72+
private string SetNotFoundNoStreamingReexecution => AppendQuery($"reexecution/set-not-found-ssr");
73+
private string SetNotFoundStreaming => AppendQuery("set-not-found-ssr-streaming");
74+
private string SetNotFoundStreamingReexecution => AppendQuery($"reexecution/set-not-found-ssr-streaming");
75+
76+
private const string PostNotFoundNoStreamingText = "PageThatPostsNotFound-no-streaming";
77+
private const string PostNotFoundNoStreamingReexecutionText = $"{PostNotFoundNoStreamingText} with reexecution";
78+
private const string PostNotFoundStreamingText = "PageThatPostsNotFound-streaming";
79+
private const string PostNotFoundStreamingReexecutionText = $"{PostNotFoundStreamingText} with reexecution";
80+
private const string SetNotFoundNoStreamingText = "PageThatSetsNotFound-no-streaming";
81+
private const string SetNotFoundNoStreamingReexecutionText = $"{SetNotFoundNoStreamingText} with reexecution";
82+
private const string SetNotFoundStreamingText = "PageThatSetsNotFound-streaming";
83+
private const string SetNotFoundStreamingReexecutionText = $"{SetNotFoundStreamingText} with reexecution";
84+
85+
private void NavigateTo(string url)
86+
{
87+
NavigationManager.NavigateTo(url);
88+
}
89+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@page "/reexecution/post-not-found-ssr"
2+
@page "/post-not-found-ssr"
3+
@attribute [StreamRendering(false)]
4+
5+
@*
6+
this page is used in global interactivity and no interactivity scenarios
7+
the content is rendered on the server without streaming and might become
8+
interactive later if interactivity was enabled in the app
9+
*@
10+
11+
<Components.Shared.ComponentThatPostsNotFound />
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@page "/reexecution/post-not-found-ssr-streaming"
2+
@page "/post-not-found-ssr-streaming"
3+
@attribute [StreamRendering(true)]
4+
5+
@*
6+
this page is used in global interactivity and no interactivity scenarios
7+
the content is rendered on the server with streaming and might become
8+
interactive later if interactivity was enabled in the app
9+
*@
10+
11+
<Components.Shared.ComponentThatPostsNotFound StartStreaming="true"/>

src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
protected override void OnParametersSet()
1212
{
13-
if (UseCustomNotFoundPage == "true")
13+
if (string.Equals(UseCustomNotFoundPage, "true", StringComparison.OrdinalIgnoreCase))
1414
{
1515
NotFoundPageType = typeof(CustomNotFoundPage);
1616
}

src/Components/test/testassets/Components.WasmMinimal/Routes.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
protected override void OnParametersSet()
1313
{
14-
if (UseCustomNotFoundPage == "true")
14+
if (string.Equals(UseCustomNotFoundPage, "true", StringComparison.OrdinalIgnoreCase))
1515
{
1616
NotFoundPageType = typeof(CustomNotFoundPage);
1717
}

src/Shared/E2ETesting/WaitAssert.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ public static void True(this IWebDriver driver, Func<bool> actual, TimeSpan time
3737

3838
public static void True(this IWebDriver driver, Func<bool> actual, TimeSpan timeout, string message)
3939
=> WaitAssertCore(driver, () => Assert.True(actual(), message), timeout);
40+
public static void True(this IWebDriver driver, Func<bool> actual, string message)
41+
=> WaitAssertCore(driver, () => Assert.True(actual(), message));
42+
43+
public static void False(this IWebDriver driver, Func<bool> actual, string message)
44+
=> WaitAssertCore(driver, () => Assert.False(actual(), message));
4045

4146
public static void False(this IWebDriver driver, Func<bool> actual)
4247
=> WaitAssertCore(driver, () => Assert.False(actual()));

0 commit comments

Comments
 (0)