diff --git a/src/FlaUI.WebDriver.UITests/SessionTests.cs b/src/FlaUI.WebDriver.UITests/SessionTests.cs index 919e411..65e6f8d 100644 --- a/src/FlaUI.WebDriver.UITests/SessionTests.cs +++ b/src/FlaUI.WebDriver.UITests/SessionTests.cs @@ -359,5 +359,15 @@ public void NewCommandTimeout_InvalidValue_Throws(object value) Assert.That(() => new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions), Throws.TypeOf().With.Message.EqualTo("Capability appium:newCommandTimeout must be a number")); } + + [Test] + public void UnknownCommand_Default_ReturnsError() + { + var driverOptions = FlaUIDriverOptions.TestApp(); + using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions); + + Assert.That(() => driver.Manage().Cookies.DeleteAllCookies(), + Throws.TypeOf().With.Message.EqualTo("Unknown command")); + } } } diff --git a/src/FlaUI.WebDriver/Controllers/ErrorController.cs b/src/FlaUI.WebDriver/Controllers/ErrorController.cs new file mode 100644 index 0000000..5c83f92 --- /dev/null +++ b/src/FlaUI.WebDriver/Controllers/ErrorController.cs @@ -0,0 +1,33 @@ +using FlaUI.WebDriver.Models; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Mvc; + +namespace FlaUI.WebDriver.Controllers +{ + [ApiController] + [ApiExplorerSettings(IgnoreApi = true)] + public class ErrorController : ControllerBase + { + private readonly ILogger _logger; + + + public ErrorController(ILogger logger) { + _logger = logger; + } + + [Route("/error")] + public IActionResult HandleError() { + var exceptionHandlerFeature = HttpContext.Features.Get()!; + + _logger.LogError(exceptionHandlerFeature.Error, "Returning WebDriver error response with error code 'unknown error'"); + + return new ObjectResult(new ResponseWithValue(new ErrorResponse { + ErrorCode = "unknown error", + Message = exceptionHandlerFeature.Error.Message, + StackTrace = exceptionHandlerFeature.Error.StackTrace ?? "" })) + { + StatusCode = 500 + }; + } + } +} diff --git a/src/FlaUI.WebDriver/NotFoundMiddleware.cs b/src/FlaUI.WebDriver/NotFoundMiddleware.cs new file mode 100644 index 0000000..49f2941 --- /dev/null +++ b/src/FlaUI.WebDriver/NotFoundMiddleware.cs @@ -0,0 +1,47 @@ +using FlaUI.WebDriver.Models; + +namespace FlaUI.WebDriver +{ + public class NotFoundMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public NotFoundMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task Invoke(HttpContext httpContext) + { + await _next(httpContext); + if (!httpContext.Response.HasStarted) + { + if (httpContext.Response.StatusCode == StatusCodes.Status404NotFound) + { + _logger.LogError("Unknown endpoint {Path}", SanitizeForLog(httpContext.Request.Path.ToString())); + await httpContext.Response.WriteAsJsonAsync(new ResponseWithValue(new ErrorResponse + { + ErrorCode = "unknown command", + Message = "Unknown command" + })); + } + else if (httpContext.Response.StatusCode == StatusCodes.Status405MethodNotAllowed) + { + _logger.LogError("Unknown method {Method} for endpoint {Path}", SanitizeForLog(httpContext.Request.Method), SanitizeForLog(httpContext.Request.Path.ToString())); + await httpContext.Response.WriteAsJsonAsync(new ResponseWithValue(new ErrorResponse + { + ErrorCode = "unknown method", + Message = "Unknown method for this endpoint" + })); + } + } + } + + private static string SanitizeForLog(string str) + { + return str.Replace("\r", "").Replace("\n", ""); + } + } +} diff --git a/src/FlaUI.WebDriver/Program.cs b/src/FlaUI.WebDriver/Program.cs index bbc78f6..eee9b99 100644 --- a/src/FlaUI.WebDriver/Program.cs +++ b/src/FlaUI.WebDriver/Program.cs @@ -24,6 +24,10 @@ var app = builder.Build(); +app.UseMiddleware(); + +app.UseExceptionHandler("/error"); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/src/FlaUI.WebDriver/Session.cs b/src/FlaUI.WebDriver/Session.cs index 52bc1b7..93aca5c 100644 --- a/src/FlaUI.WebDriver/Session.cs +++ b/src/FlaUI.WebDriver/Session.cs @@ -20,7 +20,12 @@ public Session(Application? app, bool isAppOwnedBySession) if (app != null) { // We have to capture the initial window handle to be able to keep it stable - CurrentWindowWithHandle = GetOrAddKnownWindow(app.GetMainWindow(Automation, PageLoadTimeout)); + var mainWindow = app.GetMainWindow(Automation, PageLoadTimeout); + if (mainWindow == null) + { + throw WebDriverResponseException.Timeout($"Could not get the main window of the app within the page load timeout (${PageLoadTimeout.TotalMilliseconds}ms)"); + } + CurrentWindowWithHandle = GetOrAddKnownWindow(mainWindow); } } diff --git a/src/FlaUI.WebDriver/WebDriverExceptionFilter.cs b/src/FlaUI.WebDriver/WebDriverExceptionFilter.cs index c7462f9..c01be45 100644 --- a/src/FlaUI.WebDriver/WebDriverExceptionFilter.cs +++ b/src/FlaUI.WebDriver/WebDriverExceptionFilter.cs @@ -15,13 +15,14 @@ public void OnActionExecuted(ActionExecutedContext context) if (context.Exception is WebDriverResponseException exception) { var logger = context.HttpContext.RequestServices.GetRequiredService>(); - logger.LogError(exception, "Returning WebDriver error response with error code {ErrorCode}", exception.ErrorCode); + logger.LogError(exception, "Returning WebDriver error response with error code '{ErrorCode}'", exception.ErrorCode); - context.Result = new ObjectResult(new ResponseWithValue(new ErrorResponse { ErrorCode = exception.ErrorCode, Message = exception.Message })) { + context.Result = new ObjectResult(new ResponseWithValue(new ErrorResponse { ErrorCode = exception.ErrorCode, Message = exception.Message })) + { StatusCode = exception.StatusCode }; context.ExceptionHandled = true; } } } -} +} \ No newline at end of file diff --git a/src/FlaUI.WebDriver/WebDriverResponseException.cs b/src/FlaUI.WebDriver/WebDriverResponseException.cs index 205cff5..97d74bb 100644 --- a/src/FlaUI.WebDriver/WebDriverResponseException.cs +++ b/src/FlaUI.WebDriver/WebDriverResponseException.cs @@ -24,5 +24,7 @@ private WebDriverResponseException(string message, string errorCode, int statusC public static WebDriverResponseException WindowNotFoundByHandle(string windowHandle) => new WebDriverResponseException($"No window found with handle '{windowHandle}'", "no such window", 404); public static WebDriverResponseException NoWindowsOpenForSession() => new WebDriverResponseException($"No windows are open for the current session", "no such window", 404); + + public static WebDriverResponseException Timeout(string message) => new WebDriverResponseException($"Timeout: ${message}", "timeout", 500); } }