From 366d72660d00a20aca3167767b216c04a2597456 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:05:09 +0000 Subject: [PATCH 01/12] Initial plan From f9f2e4dfaa47a17d413c2502b8af5b8e94306ce8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:09:14 +0000 Subject: [PATCH 02/12] Add JavaScript redirect test and update PuppeteerSharpWebAccessor Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 23 ++++++++++++++++ .../PuppeteerSharpWebAccessorTests.cs | 26 +++++++++++++++++++ .../TestWebApp/Pages/js-redirect.html | 16 ++++++++++++ .../TestWebApp/TestWebApplicationFactory.cs | 6 +++++ 4 files changed, 71 insertions(+) create mode 100644 test/NetInteractor.Test/TestWebApp/Pages/js-redirect.html diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index e3708bc..8438915 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -84,11 +84,34 @@ public async Task GetAsync(string url) try { + // Navigate to the URL and wait for network to be idle + // This will handle the initial page load and any immediate redirects var response = await page.GoToAsync(url, new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } }); + // After the page loads, check if JavaScript might trigger a delayed redirect + // We wait for a short period to see if a navigation event occurs + // This handles cases like: setTimeout(() => window.location.href = '/other', 500) + await Task.Delay(100); // Small initial delay to let any immediate JS execute + + try + { + // Try to wait for a navigation with a timeout + // If JavaScript triggers a redirect, we'll catch it here + response = await page.WaitForNavigationAsync(new NavigationOptions + { + WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }, + Timeout = 1500 // Wait up to 1.5 seconds for JS redirect + }); + } + catch (PuppeteerException) + { + // No additional navigation occurred - this is normal for non-redirecting pages + // Use the original response from GoToAsync + } + return await GetResultFromResponse(page, response); } finally diff --git a/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs b/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs index 320245d..f4c8806 100644 --- a/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs +++ b/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs @@ -154,5 +154,31 @@ public async Task MultipleRequests_ReusesBrowser() Assert.Contains("Example Domain", result1.Html); Assert.Contains("IANA", result2.Html); } + + [Fact(Skip = "Requires browser download. Enable for manual testing.")] + public async Task GetAsync_HandlesJavaScriptRedirect() + { + // This test verifies that PuppeteerSharp can handle JavaScript redirects + // by waiting for navigation to complete + + // Arrange + using var factory = new Test.TestWebApp.TestWebApplicationFactory(Test.TestWebApp.ServerMode.Kestrel); + var url = $"{factory.ServerUrl}/js-redirect-test"; + + // Act + var result = await _accessor.GetAsync(url); + + // Assert + Assert.NotNull(result); + Assert.Equal(200, result.StatusCode); + Assert.NotNull(result.Html); + + // Verify we got redirected to the products page + Assert.Contains("Products", result.Html); + Assert.Contains("/products", result.Url); + + // Should NOT contain the "Redirecting..." text from the original page + Assert.DoesNotContain("Redirecting...", result.Html); + } } } diff --git a/test/NetInteractor.Test/TestWebApp/Pages/js-redirect.html b/test/NetInteractor.Test/TestWebApp/Pages/js-redirect.html new file mode 100644 index 0000000..58047e4 --- /dev/null +++ b/test/NetInteractor.Test/TestWebApp/Pages/js-redirect.html @@ -0,0 +1,16 @@ + + + + JavaScript Redirect Test + + + +

Redirecting...

+

You should be redirected to the products page shortly.

+ + diff --git a/test/NetInteractor.Test/TestWebApp/TestWebApplicationFactory.cs b/test/NetInteractor.Test/TestWebApp/TestWebApplicationFactory.cs index 3cdb704..2876133 100644 --- a/test/NetInteractor.Test/TestWebApp/TestWebApplicationFactory.cs +++ b/test/NetInteractor.Test/TestWebApp/TestWebApplicationFactory.cs @@ -199,6 +199,12 @@ public TestWebApplicationFactory(ServerMode mode = ServerMode.TestServer) await context.Response.WriteAsync(html); }); + // JavaScript redirect test endpoint + _app.MapGet("/js-redirect-test", async context => + { + await context.Response.WriteAsync(LoadPage("js-redirect.html")); + }); + _app.Start(); if (mode == ServerMode.Kestrel) From 5f7ef1feca544d0c75325344b719f76e690395d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:11:33 +0000 Subject: [PATCH 03/12] Optimize JavaScript redirect handling with 1-second timeout Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index 8438915..c14309a 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -92,25 +92,22 @@ public async Task GetAsync(string url) }); // After the page loads, check if JavaScript might trigger a delayed redirect - // We wait for a short period to see if a navigation event occurs // This handles cases like: setTimeout(() => window.location.href = '/other', 500) - await Task.Delay(100); // Small initial delay to let any immediate JS execute - - try + // We race between a delay and a navigation wait + var delayTask = Task.Delay(1000); // Wait up to 1 second for potential JS redirect + var navigationTask = page.WaitForNavigationAsync(new NavigationOptions { - // Try to wait for a navigation with a timeout - // If JavaScript triggers a redirect, we'll catch it here - response = await page.WaitForNavigationAsync(new NavigationOptions - { - WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }, - Timeout = 1500 // Wait up to 1.5 seconds for JS redirect - }); - } - catch (PuppeteerException) + WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } + }); + + var completedTask = await Task.WhenAny(delayTask, navigationTask); + + if (completedTask == navigationTask) { - // No additional navigation occurred - this is normal for non-redirecting pages - // Use the original response from GoToAsync + // Navigation occurred, use the new response + response = await navigationTask; } + // else: delay completed first, meaning no navigation occurred - use original response return await GetResultFromResponse(page, response); } From a257d90dda8cca3b0e6ef54d2046e57c18b035f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:13:36 +0000 Subject: [PATCH 04/12] Make JS redirect timeout configurable and improve code comments Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index c14309a..304d947 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -15,14 +15,16 @@ public class PuppeteerSharpWebAccessor : IWebAccessor, IDisposable private readonly LaunchOptions _launchOptions; private readonly SemaphoreSlim _browserLock = new SemaphoreSlim(1, 1); private bool _disposed; + private readonly int _jsRedirectTimeout; - public PuppeteerSharpWebAccessor(LaunchOptions launchOptions = null) + public PuppeteerSharpWebAccessor(LaunchOptions launchOptions = null, int jsRedirectTimeoutMs = 1000) { _launchOptions = launchOptions ?? new LaunchOptions { Headless = true, Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" } }; + _jsRedirectTimeout = jsRedirectTimeoutMs; } private async Task GetBrowserAsync() @@ -94,7 +96,7 @@ public async Task GetAsync(string url) // After the page loads, check if JavaScript might trigger a delayed redirect // This handles cases like: setTimeout(() => window.location.href = '/other', 500) // We race between a delay and a navigation wait - var delayTask = Task.Delay(1000); // Wait up to 1 second for potential JS redirect + var delayTask = Task.Delay(_jsRedirectTimeout); var navigationTask = page.WaitForNavigationAsync(new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } @@ -108,11 +110,13 @@ public async Task GetAsync(string url) response = await navigationTask; } // else: delay completed first, meaning no navigation occurred - use original response + // Note: navigationTask will continue running in background but will be cleaned up when page closes return await GetResultFromResponse(page, response); } finally { + // Close the page. Any pending navigationTask will be aborted when the page closes. await page.CloseAsync(); } } From 876c4d47984a8d6a83afaa87d453eefda2399082 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:14:55 +0000 Subject: [PATCH 05/12] Add exception handling for navigation task completion Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index 304d947..9d38c9b 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -106,17 +106,24 @@ public async Task GetAsync(string url) if (completedTask == navigationTask) { - // Navigation occurred, use the new response - response = await navigationTask; + // Navigation occurred - await it to get the response and handle any exceptions + try + { + response = await navigationTask; + } + catch (PuppeteerException) + { + // Navigation failed or was cancelled - use original response + } } - // else: delay completed first, meaning no navigation occurred - use original response - // Note: navigationTask will continue running in background but will be cleaned up when page closes + // else: delay completed first, meaning no navigation occurred within timeout - use original response + // Note: The navigationTask will be cancelled when the page closes in the finally block return await GetResultFromResponse(page, response); } finally { - // Close the page. Any pending navigationTask will be aborted when the page closes. + // Close the page. Any pending navigation will be cancelled. await page.CloseAsync(); } } From f894b1857da596f963cef13bfc5ac899d1b45676 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:21:37 +0000 Subject: [PATCH 06/12] Move JavaScript redirect test to IntegrationTests and delete PuppeteerSharpWebAccessorTests Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- test/NetInteractor.Test/IntegrationTests.cs | 36 ++++ .../PuppeteerSharpWebAccessorTests.cs | 184 ------------------ 2 files changed, 36 insertions(+), 184 deletions(-) delete mode 100644 test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs diff --git a/test/NetInteractor.Test/IntegrationTests.cs b/test/NetInteractor.Test/IntegrationTests.cs index 6d5cc62..da686f9 100644 --- a/test/NetInteractor.Test/IntegrationTests.cs +++ b/test/NetInteractor.Test/IntegrationTests.cs @@ -393,5 +393,41 @@ public async Task TestRedirect_AfterPost_FollowsRedirect(IWebAccessor webAccesso if (webAccessor is IDisposable disposable) disposable.Dispose(); } + + [Theory] + [ClassData(typeof(WebAccessorTestData))] + public async Task TestJavaScriptRedirect_FollowsRedirect(IWebAccessor webAccessor, string baseUrl) + { + // This test verifies that JavaScript redirects are handled properly + // JavaScript redirects only work with PuppeteerSharp (browser automation) + // HttpClient cannot execute JavaScript, so we skip this test for HttpClient + if (webAccessor is HttpClientWebAccessor) + { + // Skip test for HttpClient - it cannot handle JavaScript redirects + return; + } + + // Arrange + var url = $"{baseUrl}/js-redirect-test"; + + // Act + var result = await webAccessor.GetAsync(url); + + // Assert + Assert.NotNull(result); + Assert.Equal(200, result.StatusCode); + Assert.NotNull(result.Html); + + // Verify we got redirected to the products page + Assert.Contains("Products", result.Html); + Assert.Contains("/products", result.Url); + + // Should NOT contain the "Redirecting..." text from the original page + Assert.DoesNotContain("Redirecting...", result.Html); + + // Cleanup + if (webAccessor is IDisposable disposable) + disposable.Dispose(); + } } } diff --git a/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs b/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs deleted file mode 100644 index f4c8806..0000000 --- a/test/NetInteractor.Test/PuppeteerSharpWebAccessorTests.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Threading.Tasks; -using NetInteractor.WebAccessors; -using Xunit; - -namespace NetInteractor.Test -{ - /// - /// Tests specifically for PuppeteerSharpWebAccessor. - /// These tests use real HTTP endpoints to verify browser automation functionality. - /// - public class PuppeteerSharpWebAccessorTests : IDisposable - { - private PuppeteerSharpWebAccessor _accessor; - - public PuppeteerSharpWebAccessorTests() - { - _accessor = new PuppeteerSharpWebAccessor(); - } - - public void Dispose() - { - _accessor?.Dispose(); - } - - [Fact(Skip = "Requires browser download and real network access. Enable for manual testing.")] - public async Task GetAsync_RetrievesPageContent() - { - // Arrange - var url = "https://example.com"; - - // Act - var result = await _accessor.GetAsync(url); - - // Assert - Assert.NotNull(result); - Assert.Equal(200, result.StatusCode); - Assert.NotNull(result.Html); - Assert.Contains("Example Domain", result.Html); - Assert.Contains(url, result.Url); - } - - [Fact(Skip = "Requires browser download and real network access. Enable for manual testing.")] - public async Task GetAsync_HandlesHttpsUrls() - { - // Arrange - var url = "https://www.google.com"; - - // Act - var result = await _accessor.GetAsync(url); - - // Assert - Assert.NotNull(result); - Assert.Equal(200, result.StatusCode); - Assert.NotNull(result.Html); - Assert.NotEmpty(result.Html); - } - - [Fact(Skip = "Requires browser download and real network access. Enable for manual testing.")] - public async Task GetAsync_PopulatesResponseInfo() - { - // Arrange - var url = "https://example.com"; - - // Act - var result = await _accessor.GetAsync(url); - - // Assert - Assert.NotNull(result); - Assert.InRange(result.StatusCode, 200, 299); // Success status codes - Assert.NotNull(result.Html); - Assert.NotEmpty(result.Html); - Assert.NotNull(result.Url); - Assert.NotNull(result.Headers); - } - - [Fact(Skip = "Requires browser download and real network access. Enable for manual testing.")] - public async Task GetAsync_RendersJavaScript() - { - // This test verifies that PuppeteerSharp can execute JavaScript - // which is a key advantage over HttpClient-based solutions - - // Arrange - // Use a site known to have JavaScript-rendered content - var url = "https://example.com"; - - // Act - var result = await _accessor.GetAsync(url); - - // Assert - Assert.NotNull(result); - Assert.Equal(200, result.StatusCode); - Assert.NotNull(result.Html); - // Verify we got rendered HTML (not just source) - Assert.Contains(" Date: Thu, 22 Jan 2026 01:51:28 +0000 Subject: [PATCH 07/12] Make JS redirect timeout configurable through IInteractActionConfig.Options Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 54 ++++++++++--------- src/NetInteractor/Config/CallConfig.cs | 26 +++++++++ src/NetInteractor/Config/IfConfig.cs | 25 +++++++++ .../Config/InteractActionConfig.cs | 28 ++++++++++ src/NetInteractor/IWebAccessor.cs | 5 +- src/NetInteractor/Interacts/Get.cs | 2 +- src/NetInteractor/Interacts/Post.cs | 2 +- .../Interacts/WebInteractionBase.cs | 2 +- .../WebAccessors/HttpClientWebAccessor.cs | 4 +- 9 files changed, 116 insertions(+), 32 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index 9d38c9b..395092b 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using PuppeteerSharp; +using NetInteractor.Config; namespace NetInteractor.WebAccessors { @@ -15,16 +16,14 @@ public class PuppeteerSharpWebAccessor : IWebAccessor, IDisposable private readonly LaunchOptions _launchOptions; private readonly SemaphoreSlim _browserLock = new SemaphoreSlim(1, 1); private bool _disposed; - private readonly int _jsRedirectTimeout; - public PuppeteerSharpWebAccessor(LaunchOptions launchOptions = null, int jsRedirectTimeoutMs = 1000) + public PuppeteerSharpWebAccessor(LaunchOptions launchOptions = null) { _launchOptions = launchOptions ?? new LaunchOptions { Headless = true, Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" } }; - _jsRedirectTimeout = jsRedirectTimeoutMs; } private async Task GetBrowserAsync() @@ -79,7 +78,7 @@ private async Task GetBrowserAsync() return _browser; } - public async Task GetAsync(string url) + public async Task GetAsync(string url, IInteractActionConfig config = null) { var browser = await GetBrowserAsync(); var page = await browser.NewPageAsync(); @@ -93,31 +92,36 @@ public async Task GetAsync(string url) WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } }); - // After the page loads, check if JavaScript might trigger a delayed redirect - // This handles cases like: setTimeout(() => window.location.href = '/other', 500) - // We race between a delay and a navigation wait - var delayTask = Task.Delay(_jsRedirectTimeout); - var navigationTask = page.WaitForNavigationAsync(new NavigationOptions + // Check if JavaScript redirect timeout is configured in options + var jsRedirectTimeoutStr = config?.Options?["jsRedirectTimeout"]; + if (!string.IsNullOrEmpty(jsRedirectTimeoutStr) && int.TryParse(jsRedirectTimeoutStr, out var jsRedirectTimeout)) { - WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } - }); - - var completedTask = await Task.WhenAny(delayTask, navigationTask); - - if (completedTask == navigationTask) - { - // Navigation occurred - await it to get the response and handle any exceptions - try + // After the page loads, check if JavaScript might trigger a delayed redirect + // This handles cases like: setTimeout(() => window.location.href = '/other', 500) + // We race between a delay and a navigation wait + var delayTask = Task.Delay(jsRedirectTimeout); + var navigationTask = page.WaitForNavigationAsync(new NavigationOptions { - response = await navigationTask; - } - catch (PuppeteerException) + WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } + }); + + var completedTask = await Task.WhenAny(delayTask, navigationTask); + + if (completedTask == navigationTask) { - // Navigation failed or was cancelled - use original response + // Navigation occurred - await it to get the response and handle any exceptions + try + { + response = await navigationTask; + } + catch (PuppeteerException) + { + // Navigation failed or was cancelled - use original response + } } + // else: delay completed first, meaning no navigation occurred within timeout - use original response + // Note: The navigationTask will be cancelled when the page closes in the finally block } - // else: delay completed first, meaning no navigation occurred within timeout - use original response - // Note: The navigationTask will be cancelled when the page closes in the finally block return await GetResultFromResponse(page, response); } @@ -128,7 +132,7 @@ public async Task GetAsync(string url) } } - public async Task PostAsync(string url, NameValueCollection formValues) + public async Task PostAsync(string url, NameValueCollection formValues, IInteractActionConfig config = null) { var browser = await GetBrowserAsync(); var page = await browser.NewPageAsync(); diff --git a/src/NetInteractor/Config/CallConfig.cs b/src/NetInteractor/Config/CallConfig.cs index f6990fb..ef289cd 100644 --- a/src/NetInteractor/Config/CallConfig.cs +++ b/src/NetInteractor/Config/CallConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using System.Threading.Tasks; +using System.Xml; using System.Xml.Serialization; using NetInteractor.Interacts; @@ -13,6 +14,31 @@ public class CallConfig : IInteractActionConfig [XmlAttribute("target")] public string Target { get; set; } + private NameValueCollection _options; + + [XmlAnyAttribute] + public XmlAttribute[] UnknownAttributes + { + get { return null; } + set + { + if (value != null && value.Length > 0) + { + _options = new NameValueCollection(); + foreach (var attr in value) + { + _options.Add(attr.Name, attr.Value); + } + } + } + } + + [XmlIgnore] + public NameValueCollection Options + { + get { return _options ?? (_options = new NameValueCollection()); } + } + public IInteractAction GetAction() { return new Call(this); diff --git a/src/NetInteractor/Config/IfConfig.cs b/src/NetInteractor/Config/IfConfig.cs index 00a8850..5b6921c 100644 --- a/src/NetInteractor/Config/IfConfig.cs +++ b/src/NetInteractor/Config/IfConfig.cs @@ -20,6 +20,31 @@ public class IfConfig : IInteractActionConfig, IUnknownElementHandler [XmlIgnore] public IInteractActionConfig Child { get; private set; } + private NameValueCollection _options; + + [XmlAnyAttribute] + public XmlAttribute[] UnknownAttributes + { + get { return null; } + set + { + if (value != null && value.Length > 0) + { + _options = new NameValueCollection(); + foreach (var attr in value) + { + _options.Add(attr.Name, attr.Value); + } + } + } + } + + [XmlIgnore] + public NameValueCollection Options + { + get { return _options ?? (_options = new NameValueCollection()); } + } + public IInteractAction GetAction() { return new If(this); diff --git a/src/NetInteractor/Config/InteractActionConfig.cs b/src/NetInteractor/Config/InteractActionConfig.cs index 702b8e2..e596207 100644 --- a/src/NetInteractor/Config/InteractActionConfig.cs +++ b/src/NetInteractor/Config/InteractActionConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using System.Threading.Tasks; +using System.Xml; using System.Xml.Serialization; namespace NetInteractor.Config @@ -8,6 +9,8 @@ namespace NetInteractor.Config public interface IInteractActionConfig { IInteractAction GetAction(); + + NameValueCollection Options { get; } } public abstract class InteractActionConfig : IInteractActionConfig @@ -19,6 +22,31 @@ public abstract class InteractActionConfig : IInteractActionConfig [XmlAttribute("expectedHttpStatusCodes")] public string ExpectedHttpStatusCodes { get; set; } + private NameValueCollection _options; + + [XmlAnyAttribute] + public XmlAttribute[] UnknownAttributes + { + get { return null; } + set + { + if (value != null && value.Length > 0) + { + _options = new NameValueCollection(); + foreach (var attr in value) + { + _options.Add(attr.Name, attr.Value); + } + } + } + } + + [XmlIgnore] + public NameValueCollection Options + { + get { return _options ?? (_options = new NameValueCollection()); } + } + public abstract IInteractAction GetAction(); } } \ No newline at end of file diff --git a/src/NetInteractor/IWebAccessor.cs b/src/NetInteractor/IWebAccessor.cs index b0a06aa..c652f71 100644 --- a/src/NetInteractor/IWebAccessor.cs +++ b/src/NetInteractor/IWebAccessor.cs @@ -2,13 +2,14 @@ using System.Collections.Specialized; using System.Net; using System.Threading.Tasks; +using NetInteractor.Config; namespace NetInteractor { public interface IWebAccessor { - Task GetAsync(string url); + Task GetAsync(string url, IInteractActionConfig config = null); - Task PostAsync(string url, NameValueCollection formValues); + Task PostAsync(string url, NameValueCollection formValues, IInteractActionConfig config = null); } } diff --git a/src/NetInteractor/Interacts/Get.cs b/src/NetInteractor/Interacts/Get.cs index e245feb..b27382f 100644 --- a/src/NetInteractor/Interacts/Get.cs +++ b/src/NetInteractor/Interacts/Get.cs @@ -18,7 +18,7 @@ protected override async Task MakeRequest(InterationContext contex { var url = PrepareValue(context, Config.Url); var webAccessor = context.WebAccessor; - return await webAccessor.GetAsync(url); + return await webAccessor.GetAsync(url, Config); } } } \ No newline at end of file diff --git a/src/NetInteractor/Interacts/Post.cs b/src/NetInteractor/Interacts/Post.cs index b665d93..bb2c8da 100644 --- a/src/NetInteractor/Interacts/Post.cs +++ b/src/NetInteractor/Interacts/Post.cs @@ -98,7 +98,7 @@ protected override async Task MakeRequest(InterationContext contex } } - return await webAccessor.PostAsync(url, formValues); + return await webAccessor.PostAsync(url, formValues, Config); } private NameValueCollection MergeFormValues(InterationContext context, FormInfo form, FormValue[] formValues) diff --git a/src/NetInteractor/Interacts/WebInteractionBase.cs b/src/NetInteractor/Interacts/WebInteractionBase.cs index 8308cff..1839b43 100644 --- a/src/NetInteractor/Interacts/WebInteractionBase.cs +++ b/src/NetInteractor/Interacts/WebInteractionBase.cs @@ -80,7 +80,7 @@ protected WebInteractionBase(TConfig config) private Task MakeRedirectRequest(InterationContext context, string redirectUrl) { var webAccessor = context.WebAccessor; - return webAccessor.GetAsync(redirectUrl); + return webAccessor.GetAsync(redirectUrl, Config); } private async Task MakeRequestInternal(InterationContext context, string redirectUrl = null) diff --git a/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs b/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs index 0a6639a..b48d2c5 100644 --- a/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs +++ b/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs @@ -35,7 +35,7 @@ public HttpClientWebAccessor() { } - public virtual async Task GetAsync(string url) + public virtual async Task GetAsync(string url, Config.IInteractActionConfig config = null) { var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -44,7 +44,7 @@ public virtual async Task GetAsync(string url) return await GetResultFromResponse(response); } - public virtual async Task PostAsync(string url, NameValueCollection formValues) + public virtual async Task PostAsync(string url, NameValueCollection formValues, Config.IInteractActionConfig config = null) { var request = new HttpRequestMessage(HttpMethod.Post, url); From 5faba5d425ac0abc415769405a9d372975dd6ef1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:53:22 +0000 Subject: [PATCH 08/12] Add config file for JavaScript redirect test with jsRedirectTimeout option Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- test/NetInteractor.Test/IntegrationTests.cs | 23 ++++++++----------- .../Scripts/JavaScriptRedirectTest.config | 7 ++++++ 2 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config diff --git a/test/NetInteractor.Test/IntegrationTests.cs b/test/NetInteractor.Test/IntegrationTests.cs index da686f9..a756164 100644 --- a/test/NetInteractor.Test/IntegrationTests.cs +++ b/test/NetInteractor.Test/IntegrationTests.cs @@ -398,7 +398,8 @@ public async Task TestRedirect_AfterPost_FollowsRedirect(IWebAccessor webAccesso [ClassData(typeof(WebAccessorTestData))] public async Task TestJavaScriptRedirect_FollowsRedirect(IWebAccessor webAccessor, string baseUrl) { - // This test verifies that JavaScript redirects are handled properly + // This test verifies that JavaScript redirects are handled properly when + // jsRedirectTimeout option is configured in the config // JavaScript redirects only work with PuppeteerSharp (browser automation) // HttpClient cannot execute JavaScript, so we skip this test for HttpClient if (webAccessor is HttpClientWebAccessor) @@ -408,22 +409,16 @@ public async Task TestJavaScriptRedirect_FollowsRedirect(IWebAccessor webAccesso } // Arrange - var url = $"{baseUrl}/js-redirect-test"; + var executor = new InterationExecutor(webAccessor); + var config = LoadConfig("JavaScriptRedirectTest.config"); + var inputs = new NameValueCollection { ["BaseUrl"] = baseUrl }; - // Act - var result = await webAccessor.GetAsync(url); + // Act - Should follow JavaScript redirect from /js-redirect-test to /products + var result = await executor.ExecuteAsync(config, inputs); // Assert - Assert.NotNull(result); - Assert.Equal(200, result.StatusCode); - Assert.NotNull(result.Html); - - // Verify we got redirected to the products page - Assert.Contains("Products", result.Html); - Assert.Contains("/products", result.Url); - - // Should NOT contain the "Redirecting..." text from the original page - Assert.DoesNotContain("Redirecting...", result.Html); + Assert.True(result.Ok, result.Message); + Assert.Equal("Products", result.Outputs["title"]); // Should get products page after redirect // Cleanup if (webAccessor is IDisposable disposable) diff --git a/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config b/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config new file mode 100644 index 0000000..f82cc94 --- /dev/null +++ b/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config @@ -0,0 +1,7 @@ + + + + + + + From 72aee8b2d926dd4a555c69bf0a4b804903c073b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 02:11:10 +0000 Subject: [PATCH 09/12] Rename jsRedirectTimeout to loadDelay Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 8 ++++---- test/NetInteractor.Test/IntegrationTests.cs | 2 +- .../Scripts/JavaScriptRedirectTest.config | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index 395092b..586f009 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -92,14 +92,14 @@ public async Task GetAsync(string url, IInteractActionConfig confi WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } }); - // Check if JavaScript redirect timeout is configured in options - var jsRedirectTimeoutStr = config?.Options?["jsRedirectTimeout"]; - if (!string.IsNullOrEmpty(jsRedirectTimeoutStr) && int.TryParse(jsRedirectTimeoutStr, out var jsRedirectTimeout)) + // Check if load delay is configured in options + var loadDelayStr = config?.Options?["loadDelay"]; + if (!string.IsNullOrEmpty(loadDelayStr) && int.TryParse(loadDelayStr, out var loadDelay)) { // After the page loads, check if JavaScript might trigger a delayed redirect // This handles cases like: setTimeout(() => window.location.href = '/other', 500) // We race between a delay and a navigation wait - var delayTask = Task.Delay(jsRedirectTimeout); + var delayTask = Task.Delay(loadDelay); var navigationTask = page.WaitForNavigationAsync(new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } diff --git a/test/NetInteractor.Test/IntegrationTests.cs b/test/NetInteractor.Test/IntegrationTests.cs index a756164..f558c1a 100644 --- a/test/NetInteractor.Test/IntegrationTests.cs +++ b/test/NetInteractor.Test/IntegrationTests.cs @@ -399,7 +399,7 @@ public async Task TestRedirect_AfterPost_FollowsRedirect(IWebAccessor webAccesso public async Task TestJavaScriptRedirect_FollowsRedirect(IWebAccessor webAccessor, string baseUrl) { // This test verifies that JavaScript redirects are handled properly when - // jsRedirectTimeout option is configured in the config + // loadDelay option is configured in the config // JavaScript redirects only work with PuppeteerSharp (browser automation) // HttpClient cannot execute JavaScript, so we skip this test for HttpClient if (webAccessor is HttpClientWebAccessor) diff --git a/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config b/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config index f82cc94..cca4343 100644 --- a/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config +++ b/test/NetInteractor.Test/Scripts/JavaScriptRedirectTest.config @@ -1,6 +1,6 @@ - + From cf1ae5e856ff352bf6d80debc91ceeedfa6ee253 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 02:38:54 +0000 Subject: [PATCH 10/12] Refactor Options to only exist in InteractActionConfig base class Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 4 +-- src/NetInteractor/Config/CallConfig.cs | 25 ------------------- src/NetInteractor/Config/IfConfig.cs | 25 ------------------- .../Config/InteractActionConfig.cs | 2 -- src/NetInteractor/IWebAccessor.cs | 4 +-- .../WebAccessors/HttpClientWebAccessor.cs | 4 +-- 6 files changed, 6 insertions(+), 58 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index 586f009..2d2e638 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -78,7 +78,7 @@ private async Task GetBrowserAsync() return _browser; } - public async Task GetAsync(string url, IInteractActionConfig config = null) + public async Task GetAsync(string url, InteractActionConfig config = null) { var browser = await GetBrowserAsync(); var page = await browser.NewPageAsync(); @@ -132,7 +132,7 @@ public async Task GetAsync(string url, IInteractActionConfig confi } } - public async Task PostAsync(string url, NameValueCollection formValues, IInteractActionConfig config = null) + public async Task PostAsync(string url, NameValueCollection formValues, InteractActionConfig config = null) { var browser = await GetBrowserAsync(); var page = await browser.NewPageAsync(); diff --git a/src/NetInteractor/Config/CallConfig.cs b/src/NetInteractor/Config/CallConfig.cs index ef289cd..95091b7 100644 --- a/src/NetInteractor/Config/CallConfig.cs +++ b/src/NetInteractor/Config/CallConfig.cs @@ -14,31 +14,6 @@ public class CallConfig : IInteractActionConfig [XmlAttribute("target")] public string Target { get; set; } - private NameValueCollection _options; - - [XmlAnyAttribute] - public XmlAttribute[] UnknownAttributes - { - get { return null; } - set - { - if (value != null && value.Length > 0) - { - _options = new NameValueCollection(); - foreach (var attr in value) - { - _options.Add(attr.Name, attr.Value); - } - } - } - } - - [XmlIgnore] - public NameValueCollection Options - { - get { return _options ?? (_options = new NameValueCollection()); } - } - public IInteractAction GetAction() { return new Call(this); diff --git a/src/NetInteractor/Config/IfConfig.cs b/src/NetInteractor/Config/IfConfig.cs index 5b6921c..00a8850 100644 --- a/src/NetInteractor/Config/IfConfig.cs +++ b/src/NetInteractor/Config/IfConfig.cs @@ -20,31 +20,6 @@ public class IfConfig : IInteractActionConfig, IUnknownElementHandler [XmlIgnore] public IInteractActionConfig Child { get; private set; } - private NameValueCollection _options; - - [XmlAnyAttribute] - public XmlAttribute[] UnknownAttributes - { - get { return null; } - set - { - if (value != null && value.Length > 0) - { - _options = new NameValueCollection(); - foreach (var attr in value) - { - _options.Add(attr.Name, attr.Value); - } - } - } - } - - [XmlIgnore] - public NameValueCollection Options - { - get { return _options ?? (_options = new NameValueCollection()); } - } - public IInteractAction GetAction() { return new If(this); diff --git a/src/NetInteractor/Config/InteractActionConfig.cs b/src/NetInteractor/Config/InteractActionConfig.cs index e596207..68aceae 100644 --- a/src/NetInteractor/Config/InteractActionConfig.cs +++ b/src/NetInteractor/Config/InteractActionConfig.cs @@ -9,8 +9,6 @@ namespace NetInteractor.Config public interface IInteractActionConfig { IInteractAction GetAction(); - - NameValueCollection Options { get; } } public abstract class InteractActionConfig : IInteractActionConfig diff --git a/src/NetInteractor/IWebAccessor.cs b/src/NetInteractor/IWebAccessor.cs index c652f71..dc37e3d 100644 --- a/src/NetInteractor/IWebAccessor.cs +++ b/src/NetInteractor/IWebAccessor.cs @@ -8,8 +8,8 @@ namespace NetInteractor { public interface IWebAccessor { - Task GetAsync(string url, IInteractActionConfig config = null); + Task GetAsync(string url, InteractActionConfig config = null); - Task PostAsync(string url, NameValueCollection formValues, IInteractActionConfig config = null); + Task PostAsync(string url, NameValueCollection formValues, InteractActionConfig config = null); } } diff --git a/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs b/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs index b48d2c5..bd059ef 100644 --- a/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs +++ b/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs @@ -35,7 +35,7 @@ public HttpClientWebAccessor() { } - public virtual async Task GetAsync(string url, Config.IInteractActionConfig config = null) + public virtual async Task GetAsync(string url, Config.InteractActionConfig config = null) { var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -44,7 +44,7 @@ public virtual async Task GetAsync(string url, Config.IInteractAct return await GetResultFromResponse(response); } - public virtual async Task PostAsync(string url, NameValueCollection formValues, Config.IInteractActionConfig config = null) + public virtual async Task PostAsync(string url, NameValueCollection formValues, Config.InteractActionConfig config = null) { var request = new HttpRequestMessage(HttpMethod.Post, url); From 5c0d1bcadfdfb352ee6b5f07b480005e81758978 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 02:56:10 +0000 Subject: [PATCH 11/12] Address PR feedback: simplify Options and add using statement Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../PuppeteerSharpWebAccessor.cs | 2 +- src/NetInteractor/Config/CallConfig.cs | 25 +++++++++++++++++++ src/NetInteractor/Config/IfConfig.cs | 25 +++++++++++++++++++ .../Config/InteractActionConfig.cs | 24 +----------------- .../WebAccessors/HttpClientWebAccessor.cs | 5 ++-- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs index 2d2e638..84014a7 100644 --- a/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs +++ b/src/NetInteractor.PuppeteerSharp/PuppeteerSharpWebAccessor.cs @@ -93,7 +93,7 @@ public async Task GetAsync(string url, InteractActionConfig config }); // Check if load delay is configured in options - var loadDelayStr = config?.Options?["loadDelay"]; + var loadDelayStr = config?.Options?.FirstOrDefault(attr => attr.Name == "loadDelay")?.Value; if (!string.IsNullOrEmpty(loadDelayStr) && int.TryParse(loadDelayStr, out var loadDelay)) { // After the page loads, check if JavaScript might trigger a delayed redirect diff --git a/src/NetInteractor/Config/CallConfig.cs b/src/NetInteractor/Config/CallConfig.cs index 95091b7..ef289cd 100644 --- a/src/NetInteractor/Config/CallConfig.cs +++ b/src/NetInteractor/Config/CallConfig.cs @@ -14,6 +14,31 @@ public class CallConfig : IInteractActionConfig [XmlAttribute("target")] public string Target { get; set; } + private NameValueCollection _options; + + [XmlAnyAttribute] + public XmlAttribute[] UnknownAttributes + { + get { return null; } + set + { + if (value != null && value.Length > 0) + { + _options = new NameValueCollection(); + foreach (var attr in value) + { + _options.Add(attr.Name, attr.Value); + } + } + } + } + + [XmlIgnore] + public NameValueCollection Options + { + get { return _options ?? (_options = new NameValueCollection()); } + } + public IInteractAction GetAction() { return new Call(this); diff --git a/src/NetInteractor/Config/IfConfig.cs b/src/NetInteractor/Config/IfConfig.cs index 00a8850..5b6921c 100644 --- a/src/NetInteractor/Config/IfConfig.cs +++ b/src/NetInteractor/Config/IfConfig.cs @@ -20,6 +20,31 @@ public class IfConfig : IInteractActionConfig, IUnknownElementHandler [XmlIgnore] public IInteractActionConfig Child { get; private set; } + private NameValueCollection _options; + + [XmlAnyAttribute] + public XmlAttribute[] UnknownAttributes + { + get { return null; } + set + { + if (value != null && value.Length > 0) + { + _options = new NameValueCollection(); + foreach (var attr in value) + { + _options.Add(attr.Name, attr.Value); + } + } + } + } + + [XmlIgnore] + public NameValueCollection Options + { + get { return _options ?? (_options = new NameValueCollection()); } + } + public IInteractAction GetAction() { return new If(this); diff --git a/src/NetInteractor/Config/InteractActionConfig.cs b/src/NetInteractor/Config/InteractActionConfig.cs index 68aceae..aefd662 100644 --- a/src/NetInteractor/Config/InteractActionConfig.cs +++ b/src/NetInteractor/Config/InteractActionConfig.cs @@ -20,30 +20,8 @@ public abstract class InteractActionConfig : IInteractActionConfig [XmlAttribute("expectedHttpStatusCodes")] public string ExpectedHttpStatusCodes { get; set; } - private NameValueCollection _options; - [XmlAnyAttribute] - public XmlAttribute[] UnknownAttributes - { - get { return null; } - set - { - if (value != null && value.Length > 0) - { - _options = new NameValueCollection(); - foreach (var attr in value) - { - _options.Add(attr.Name, attr.Value); - } - } - } - } - - [XmlIgnore] - public NameValueCollection Options - { - get { return _options ?? (_options = new NameValueCollection()); } - } + public XmlAttribute[] Options { get; set; } public abstract IInteractAction GetAction(); } diff --git a/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs b/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs index bd059ef..980538a 100644 --- a/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs +++ b/src/NetInteractor/WebAccessors/HttpClientWebAccessor.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Threading.Tasks; using System.Text; +using NetInteractor.Config; namespace NetInteractor.WebAccessors { @@ -35,7 +36,7 @@ public HttpClientWebAccessor() { } - public virtual async Task GetAsync(string url, Config.InteractActionConfig config = null) + public virtual async Task GetAsync(string url, InteractActionConfig config = null) { var request = new HttpRequestMessage(HttpMethod.Get, url); @@ -44,7 +45,7 @@ public virtual async Task GetAsync(string url, Config.InteractActi return await GetResultFromResponse(response); } - public virtual async Task PostAsync(string url, NameValueCollection formValues, Config.InteractActionConfig config = null) + public virtual async Task PostAsync(string url, NameValueCollection formValues, InteractActionConfig config = null) { var request = new HttpRequestMessage(HttpMethod.Post, url); From a911bb192b2fa2a82857f88f3c1493e3d47975d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 05:45:17 +0000 Subject: [PATCH 12/12] Revert all changes in IfConfig and CallConfig to original state Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- src/NetInteractor/Config/CallConfig.cs | 26 -------------------------- src/NetInteractor/Config/IfConfig.cs | 25 ------------------------- 2 files changed, 51 deletions(-) diff --git a/src/NetInteractor/Config/CallConfig.cs b/src/NetInteractor/Config/CallConfig.cs index ef289cd..f6990fb 100644 --- a/src/NetInteractor/Config/CallConfig.cs +++ b/src/NetInteractor/Config/CallConfig.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Specialized; using System.Threading.Tasks; -using System.Xml; using System.Xml.Serialization; using NetInteractor.Interacts; @@ -14,31 +13,6 @@ public class CallConfig : IInteractActionConfig [XmlAttribute("target")] public string Target { get; set; } - private NameValueCollection _options; - - [XmlAnyAttribute] - public XmlAttribute[] UnknownAttributes - { - get { return null; } - set - { - if (value != null && value.Length > 0) - { - _options = new NameValueCollection(); - foreach (var attr in value) - { - _options.Add(attr.Name, attr.Value); - } - } - } - } - - [XmlIgnore] - public NameValueCollection Options - { - get { return _options ?? (_options = new NameValueCollection()); } - } - public IInteractAction GetAction() { return new Call(this); diff --git a/src/NetInteractor/Config/IfConfig.cs b/src/NetInteractor/Config/IfConfig.cs index 5b6921c..00a8850 100644 --- a/src/NetInteractor/Config/IfConfig.cs +++ b/src/NetInteractor/Config/IfConfig.cs @@ -20,31 +20,6 @@ public class IfConfig : IInteractActionConfig, IUnknownElementHandler [XmlIgnore] public IInteractActionConfig Child { get; private set; } - private NameValueCollection _options; - - [XmlAnyAttribute] - public XmlAttribute[] UnknownAttributes - { - get { return null; } - set - { - if (value != null && value.Length > 0) - { - _options = new NameValueCollection(); - foreach (var attr in value) - { - _options.Add(attr.Name, attr.Value); - } - } - } - } - - [XmlIgnore] - public NameValueCollection Options - { - get { return _options ?? (_options = new NameValueCollection()); } - } - public IInteractAction GetAction() { return new If(this);