Skip to content

Commit 0586b06

Browse files
authored
fix(webSocketRoute): support no trailing slash (#3087)
1 parent 9c3d53f commit 0586b06

File tree

3 files changed

+62
-17
lines changed

3 files changed

+62
-17
lines changed

src/Playwright.Tests/PageRouteWebSocketTests.cs

+35
Original file line numberDiff line numberDiff line change
@@ -311,4 +311,39 @@ await AssertAreEqualWithRetriesAsync(() => page.EvaluateAsync<string[]>("() => w
311311
$"message: data=echo origin=ws://localhost:{Server.Port} lastEventId=",
312312
});
313313
}
314+
315+
[PlaywrightTest("page-route-web-socket.spec.ts", "should work with no trailing slash")]
316+
public async Task ShouldWorkWithNoTrailingSlash()
317+
{
318+
var log = new List<string>();
319+
// No trailing slash!
320+
await Page.RouteWebSocketAsync($"ws://localhost:{Server.Port}", ws =>
321+
{
322+
ws.OnMessage(message =>
323+
{
324+
log.Add(message.Text);
325+
ws.Send("response");
326+
});
327+
});
328+
329+
await Page.GotoAsync("about:blank");
330+
await Page.EvaluateAsync(@"({ port }) => {
331+
window.log = [];
332+
// No trailing slash in WebSocket URL
333+
window.ws = new WebSocket('ws://localhost:' + port);
334+
window.ws.addEventListener('message', event => window.log.push(event.data));
335+
}", new Dictionary<string, object>
336+
{
337+
["port"] = Server.Port
338+
});
339+
340+
await Page.WaitForFunctionAsync("() => window.ws.readyState === 1");
341+
await Page.EvaluateAsync("() => window.ws.send('query')");
342+
await AssertAreEqualWithRetriesAsync(
343+
() => Task.FromResult(log.ToArray()),
344+
["query"]);
345+
await AssertAreEqualWithRetriesAsync(
346+
() => Page.EvaluateAsync<string[]>("() => window.log"),
347+
["response"]);
348+
}
314349
}

src/Playwright/Core/PageAssertions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public Task ToHaveTitleAsync(Regex titleOrRegExp, PageAssertionsToHaveTitleOptio
5858
ExpectImplAsync("to.have.title", ExpectedRegex(titleOrRegExp, new() { NormalizeWhiteSpace = true }), titleOrRegExp, "Page title expected to be", ConvertToFrameExpectOptions(options));
5959

6060
public Task ToHaveURLAsync(string urlOrRegExp, PageAssertionsToHaveURLOptions options = null) =>
61-
ExpectImplAsync("to.have.url", new ExpectedTextValue() { String = URLMatch.JoinWithBaseURL(_page.Context.Options.BaseURL, urlOrRegExp), IgnoreCase = options?.IgnoreCase }, urlOrRegExp, "Page URL expected to be", ConvertToFrameExpectOptions(options));
61+
ExpectImplAsync("to.have.url", new ExpectedTextValue() { String = URLMatch.ConstructURLBasedOnBaseURL(_page.Context.Options.BaseURL, urlOrRegExp), IgnoreCase = options?.IgnoreCase }, urlOrRegExp, "Page URL expected to be", ConvertToFrameExpectOptions(options));
6262

6363
public Task ToHaveURLAsync(Regex urlOrRegExp, PageAssertionsToHaveURLOptions options = null) =>
6464
ExpectImplAsync("to.have.url", ExpectedRegex(urlOrRegExp, new() { IgnoreCase = options?.IgnoreCase }), urlOrRegExp, "Page URL expected to match regex", ConvertToFrameExpectOptions(options));

src/Playwright/Helpers/URLMatch.cs

+26-16
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public class URLMatch
3838
public string baseURL { get; set; }
3939

4040
public bool Match(string url)
41+
{
42+
return MatchImpl(url, re, func, glob, baseURL);
43+
}
44+
45+
private static bool MatchImpl(string url, Regex re, Func<string, bool> func, string glob, string baseURL)
4146
{
4247
if (re != null)
4348
{
@@ -51,33 +56,38 @@ public bool Match(string url)
5156

5257
if (glob != null)
5358
{
54-
var globWithBaseURL = JoinWithBaseURL(baseURL, glob);
55-
// Allow http(s) baseURL to match ws(s) urls.
56-
if (new Regex("^https?://").IsMatch(globWithBaseURL) && new Regex("^wss?://").IsMatch(url))
59+
if (string.IsNullOrEmpty(glob))
5760
{
58-
globWithBaseURL = new Regex("^http").Replace(globWithBaseURL, "ws");
61+
return true;
5962
}
60-
return new Regex(globWithBaseURL.GlobToRegex()).IsMatch(url);
63+
if (!glob.StartsWith("*", StringComparison.InvariantCultureIgnoreCase))
64+
{
65+
// Allow http(s) baseURL to match ws(s) urls.
66+
if (!string.IsNullOrEmpty(baseURL) && new Regex("^https?://").IsMatch(baseURL) && new Regex("^wss?://").IsMatch(url))
67+
{
68+
baseURL = new Regex("^http").Replace(baseURL, "ws");
69+
}
70+
glob = ConstructURLBasedOnBaseURL(baseURL, glob);
71+
}
72+
return new Regex(glob.GlobToRegex()).IsMatch(url);
6173
}
6274
return true;
6375
}
6476

65-
internal static string JoinWithBaseURL(string baseUrl, string url)
77+
internal static string ConstructURLBasedOnBaseURL(string baseUrl, string url)
6678
{
67-
if (string.IsNullOrEmpty(baseUrl)
68-
|| (url?.StartsWith("*", StringComparison.InvariantCultureIgnoreCase) ?? false)
69-
|| !Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
79+
try
7080
{
71-
return url;
81+
if (string.IsNullOrEmpty(baseUrl))
82+
{
83+
return new Uri(url, UriKind.Absolute).ToString();
84+
}
85+
return new Uri(new Uri(baseUrl), new Uri(url, UriKind.RelativeOrAbsolute)).ToString();
7286
}
73-
74-
var mUri = new Uri(url, UriKind.RelativeOrAbsolute);
75-
if (!mUri.IsAbsoluteUri)
87+
catch
7688
{
77-
return new Uri(new Uri(baseUrl), mUri).ToString();
89+
return url;
7890
}
79-
80-
return url;
8191
}
8292

8393
public bool Equals(string globMatch, Regex reMatch, Func<string, bool> funcMatch, string baseURL)

0 commit comments

Comments
 (0)