Skip to content

Commit 451a560

Browse files
authored
Merge pull request #3 from kerryjiang/copilot/add-unit-test-for-redirect
Revert Options implementation from CallConfig and IfConfig
2 parents f8cdea2 + a911bb1 commit 451a560

12 files changed

Lines changed: 110 additions & 167 deletions

File tree

src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading;
77
using System.Threading.Tasks;
88
using PuppeteerSharp;
9+
using NetInteractor.Config;
910

1011
namespace NetInteractor.WebAccessors
1112
{
@@ -77,27 +78,61 @@ private async Task<IBrowser> GetBrowserAsync()
7778
return _browser;
7879
}
7980

80-
public async Task<ResponseInfo> GetAsync(string url)
81+
public async Task<ResponseInfo> GetAsync(string url, InteractActionConfig config = null)
8182
{
8283
var browser = await GetBrowserAsync();
8384
var page = await browser.NewPageAsync();
8485

8586
try
8687
{
88+
// Navigate to the URL and wait for network to be idle
89+
// This will handle the initial page load and any immediate redirects
8790
var response = await page.GoToAsync(url, new NavigationOptions
8891
{
8992
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
9093
});
9194

95+
// Check if load delay is configured in options
96+
var loadDelayStr = config?.Options?.FirstOrDefault(attr => attr.Name == "loadDelay")?.Value;
97+
if (!string.IsNullOrEmpty(loadDelayStr) && int.TryParse(loadDelayStr, out var loadDelay))
98+
{
99+
// After the page loads, check if JavaScript might trigger a delayed redirect
100+
// This handles cases like: setTimeout(() => window.location.href = '/other', 500)
101+
// We race between a delay and a navigation wait
102+
var delayTask = Task.Delay(loadDelay);
103+
var navigationTask = page.WaitForNavigationAsync(new NavigationOptions
104+
{
105+
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
106+
});
107+
108+
var completedTask = await Task.WhenAny(delayTask, navigationTask);
109+
110+
if (completedTask == navigationTask)
111+
{
112+
// Navigation occurred - await it to get the response and handle any exceptions
113+
try
114+
{
115+
response = await navigationTask;
116+
}
117+
catch (PuppeteerException)
118+
{
119+
// Navigation failed or was cancelled - use original response
120+
}
121+
}
122+
// else: delay completed first, meaning no navigation occurred within timeout - use original response
123+
// Note: The navigationTask will be cancelled when the page closes in the finally block
124+
}
125+
92126
return await GetResultFromResponse(page, response);
93127
}
94128
finally
95129
{
130+
// Close the page. Any pending navigation will be cancelled.
96131
await page.CloseAsync();
97132
}
98133
}
99134

100-
public async Task<ResponseInfo> PostAsync(string url, NameValueCollection formValues)
135+
public async Task<ResponseInfo> PostAsync(string url, NameValueCollection formValues, InteractActionConfig config = null)
101136
{
102137
var browser = await GetBrowserAsync();
103138
var page = await browser.NewPageAsync();

src/NetInteractor/Config/InteractActionConfig.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Specialized;
33
using System.Threading.Tasks;
4+
using System.Xml;
45
using System.Xml.Serialization;
56

67
namespace NetInteractor.Config
@@ -19,6 +20,9 @@ public abstract class InteractActionConfig : IInteractActionConfig
1920
[XmlAttribute("expectedHttpStatusCodes")]
2021
public string ExpectedHttpStatusCodes { get; set; }
2122

23+
[XmlAnyAttribute]
24+
public XmlAttribute[] Options { get; set; }
25+
2226
public abstract IInteractAction GetAction();
2327
}
2428
}

src/NetInteractor/IWebAccessor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
using System.Collections.Specialized;
33
using System.Net;
44
using System.Threading.Tasks;
5+
using NetInteractor.Config;
56

67
namespace NetInteractor
78
{
89
public interface IWebAccessor
910
{
10-
Task<ResponseInfo> GetAsync(string url);
11+
Task<ResponseInfo> GetAsync(string url, InteractActionConfig config = null);
1112

12-
Task<ResponseInfo> PostAsync(string url, NameValueCollection formValues);
13+
Task<ResponseInfo> PostAsync(string url, NameValueCollection formValues, InteractActionConfig config = null);
1314
}
1415
}

src/NetInteractor/Interacts/Get.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ protected override async Task<ResponseInfo> MakeRequest(InterationContext contex
1818
{
1919
var url = PrepareValue(context, Config.Url);
2020
var webAccessor = context.WebAccessor;
21-
return await webAccessor.GetAsync(url);
21+
return await webAccessor.GetAsync(url, Config);
2222
}
2323
}
2424
}

src/NetInteractor/Interacts/Post.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ protected override async Task<ResponseInfo> MakeRequest(InterationContext contex
9898
}
9999
}
100100

101-
return await webAccessor.PostAsync(url, formValues);
101+
return await webAccessor.PostAsync(url, formValues, Config);
102102
}
103103

104104
private NameValueCollection MergeFormValues(InterationContext context, FormInfo form, FormValue[] formValues)

src/NetInteractor/Interacts/WebInteractionBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ protected WebInteractionBase(TConfig config)
8080
private Task<ResponseInfo> MakeRedirectRequest(InterationContext context, string redirectUrl)
8181
{
8282
var webAccessor = context.WebAccessor;
83-
return webAccessor.GetAsync(redirectUrl);
83+
return webAccessor.GetAsync(redirectUrl, Config);
8484
}
8585

8686
private async Task<ResponseInfo> MakeRequestInternal(InterationContext context, string redirectUrl = null)

src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net.Http;
66
using System.Threading.Tasks;
77
using System.Text;
8+
using NetInteractor.Config;
89

910
namespace NetInteractor.WebAccessors
1011
{
@@ -35,7 +36,7 @@ public HttpClientWebAccessor()
3536
{
3637
}
3738

38-
public virtual async Task<ResponseInfo> GetAsync(string url)
39+
public virtual async Task<ResponseInfo> GetAsync(string url, InteractActionConfig config = null)
3940
{
4041
var request = new HttpRequestMessage(HttpMethod.Get, url);
4142

@@ -44,7 +45,7 @@ public virtual async Task<ResponseInfo> GetAsync(string url)
4445
return await GetResultFromResponse(response);
4546
}
4647

47-
public virtual async Task<ResponseInfo> PostAsync(string url, NameValueCollection formValues)
48+
public virtual async Task<ResponseInfo> PostAsync(string url, NameValueCollection formValues, InteractActionConfig config = null)
4849
{
4950
var request = new HttpRequestMessage(HttpMethod.Post, url);
5051

test/NetInteractor.Test/IntegrationTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,5 +393,36 @@ public async Task TestRedirect_AfterPost_FollowsRedirect(IWebAccessor webAccesso
393393
if (webAccessor is IDisposable disposable)
394394
disposable.Dispose();
395395
}
396+
397+
[Theory]
398+
[ClassData(typeof(WebAccessorTestData))]
399+
public async Task TestJavaScriptRedirect_FollowsRedirect(IWebAccessor webAccessor, string baseUrl)
400+
{
401+
// This test verifies that JavaScript redirects are handled properly when
402+
// loadDelay option is configured in the config
403+
// JavaScript redirects only work with PuppeteerSharp (browser automation)
404+
// HttpClient cannot execute JavaScript, so we skip this test for HttpClient
405+
if (webAccessor is HttpClientWebAccessor)
406+
{
407+
// Skip test for HttpClient - it cannot handle JavaScript redirects
408+
return;
409+
}
410+
411+
// Arrange
412+
var executor = new InterationExecutor(webAccessor);
413+
var config = LoadConfig("JavaScriptRedirectTest.config");
414+
var inputs = new NameValueCollection { ["BaseUrl"] = baseUrl };
415+
416+
// Act - Should follow JavaScript redirect from /js-redirect-test to /products
417+
var result = await executor.ExecuteAsync(config, inputs);
418+
419+
// Assert
420+
Assert.True(result.Ok, result.Message);
421+
Assert.Equal("Products", result.Outputs["title"]); // Should get products page after redirect
422+
423+
// Cleanup
424+
if (webAccessor is IDisposable disposable)
425+
disposable.Dispose();
426+
}
396427
}
397428
}

test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs

Lines changed: 0 additions & 158 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<InteractConfig defaultTarget='Main'>
2+
<target name='Main'>
3+
<get url='$(BaseUrl)/js-redirect-test' loadDelay='1500'>
4+
<output name='title' xpath='//h1' attr='text()' />
5+
</get>
6+
</target>
7+
</InteractConfig>

0 commit comments

Comments
 (0)