diff --git a/.agents/skills/aspire/references/agent-workflows.md b/.agents/skills/aspire/references/agent-workflows.md index e8353aad8df..3f244f061ce 100644 --- a/.agents/skills/aspire/references/agent-workflows.md +++ b/.agents/skills/aspire/references/agent-workflows.md @@ -33,6 +33,85 @@ Inspect the live app before editing code: 4. `aspire otel traces ` to follow cross-service activity. 5. `aspire export` when you need a zipped telemetry snapshot for deeper analysis or handoff. +## Scenario: I Need To Drive A Browser In An Aspire App + +If the app exposes a Browser Automation resource, use it as the first-choice agent browser before reaching for Playwright. Browser Automation is optimized for frontend applications and keeps browser actions, console logs, network events, screenshots, cookies, storage, and session state tied to the Aspire resource. + +If the AppHost does not expose browser automation for the frontend yet, add the frontend-focused browser integration before falling back to Playwright: + +```bash +aspire add browsers +``` + +Attach browser automation to the frontend resource, not to backend services. For TypeScript AppHosts with a Vite frontend: + +```typescript +const frontend = await builder + .addViteApp("frontend", "./frontend") + .withExternalHttpEndpoints() + .withBrowserAutomation(); +``` + +For TypeScript AppHosts with a generic JavaScript frontend: + +```typescript +const frontend = await builder + .addJavaScriptApp("frontend", "./frontend", { runScriptName: "dev" }) + .withHttpEndpoint({ env: "PORT" }) + .withExternalHttpEndpoints() + .withBrowserAutomation(); +``` + +For C# AppHosts: + +```csharp +builder.AddProject("web") + .WithExternalHttpEndpoints() + .WithBrowserAutomation(); +``` + +For TypeScript AppHosts, run `aspire add browsers`, then inspect `.modules/aspire.ts` for the generated `withBrowserAutomation` API before editing `apphost.ts`. + +Start the app and find the browser automation resource. The resource is currently named like `-browser-automation`: + +```bash +aspire start --isolated +aspire describe --format Json +aspire resource open-tracked-browser +aspire resource inspect-browser +``` + +Use refs and snapshots for the agent loop: + +```bash +aspire resource click-browser e1 snapshotAfter=true +aspire resource type-browser-text e2 "hello" snapshotAfter=true +aspire resource wait selector='#results' timeoutMilliseconds=10000 +aspire resource get text '#results' +``` + +Use state and session commands when tests need continuity or low-level browser control: + +```bash +aspire resource state get +aspire resource state set '' true +aspire resource cookies set session abc +aspire resource storage local set theme dark +aspire resource tabs list +aspire resource frames +aspire resource dialog accept +aspire resource downloads allow /tmp/downloads true +aspire resource upload '#file' '["/tmp/file.txt"]' +aspire resource cdp Runtime.evaluate '{"expression":"document.title","returnByValue":true}' page +``` + +Keep these points in mind: + +- Re-run `inspect-browser` after navigation or major DOM changes because refs are snapshot-scoped. +- Prefer `snapshotAfter=true` on mutating commands when the next agent decision depends on the resulting DOM. +- Browser Automation cookie/state commands are page-origin scoped and cannot read or set HttpOnly cookies. +- Use Playwright only when you need independent contexts, browser matrix testing, tracing/video, or an existing Playwright test suite. + ## Scenario: I Need To Add An Integration, Understand An API, Or Add A Custom Command Safely Use the docs commands first for the workflow, then use the API reference commands if you need the concrete API entry: diff --git a/.agents/skills/aspire/references/playwright-handoff.md b/.agents/skills/aspire/references/playwright-handoff.md index 4c22b230935..4cab4450e0c 100644 --- a/.agents/skills/aspire/references/playwright-handoff.md +++ b/.agents/skills/aspire/references/playwright-handoff.md @@ -2,6 +2,59 @@ Use this when Playwright CLI is already configured and the next step is browser testing against a running Aspire app. +Before handing off to Playwright, check whether the AppHost has a Browser Automation resource for the frontend. Browser Automation is optimized for frontend applications, exposes an agent browser interface through `aspire resource ...`, and keeps browser console/network telemetry attached to the Aspire resource. + +If the AppHost does not have browser automation yet, run the frontend-focused browser integration alias and attach it to the frontend before falling back to Playwright: + +```bash +aspire add browsers +``` + +TypeScript AppHost with Vite frontend: + +```typescript +const frontend = await builder + .addViteApp("frontend", "./frontend") + .withExternalHttpEndpoints() + .withBrowserAutomation(); +``` + +TypeScript AppHost with a generic JavaScript frontend: + +```typescript +const frontend = await builder + .addJavaScriptApp("frontend", "./frontend", { runScriptName: "dev" }) + .withHttpEndpoint({ env: "PORT" }) + .withExternalHttpEndpoints() + .withBrowserAutomation(); +``` + +C# AppHost: + +```csharp +builder.AddProject("web") + .WithExternalHttpEndpoints() + .WithBrowserAutomation(); +``` + +Prefer Browser Automation resource commands when the task can be handled by the tracked browser: + +```bash +aspire resource open-tracked-browser +aspire resource inspect-browser +aspire resource click-browser e1 snapshotAfter=true +aspire resource fill-browser '#email' 'user@example.com' snapshotAfter=true +aspire resource wait selector='#results' timeoutMilliseconds=10000 +aspire resource get text '#results' +aspire resource state get +aspire resource storage local set theme dark +aspire resource cookies get +aspire resource tabs list +aspire resource cdp Target.getTargets '{}' browser +``` + +Use Playwright when the task needs capabilities outside the tracked browser resource, such as independent browser contexts, cross-browser matrix testing, tracing/video, or a test suite already written in Playwright. + ## Scenario: I Need The Right Frontend URL Before Browser Testing Use these commands when the task is to discover the live frontend endpoint from Aspire state and then hand that URL to Playwright. @@ -15,6 +68,11 @@ playwright-cli --help Keep these points in mind: - Aspire discovers the endpoint first; Playwright uses the discovered endpoint after the handoff. +- If a Browser Automation resource exists, try its resource commands first so logs, network events, screenshots, and state changes stay correlated with Aspire telemetry. +- `inspect-browser` returns element refs such as `e1`; use refs quickly and re-run `inspect-browser` after navigation or major DOM changes. +- Mutating commands commonly support `snapshotAfter=true` to return a fresh page snapshot for agent verification. +- Browser Automation `state`, `cookies`, and `storage` operate in the active page origin and only expose page-visible cookies; use Playwright browser contexts when HttpOnly cookies or multi-origin storage state are required. +- Use the raw `cdp` command as the escape hatch for low-level browser protocol operations before leaving Aspire. - Prefer `aspire describe --format Json` when the URL needs to be consumed by a script or passed to another tool. - Use `--apphost ` when multiple AppHosts exist and the user is asking about one specific app. - Do not guess frontend endpoints without first consulting Aspire state. diff --git a/.agents/skills/aspire/references/typescript-apphosts.md b/.agents/skills/aspire/references/typescript-apphosts.md index 09b3a12301c..49432c490e7 100644 --- a/.agents/skills/aspire/references/typescript-apphosts.md +++ b/.agents/skills/aspire/references/typescript-apphosts.md @@ -19,6 +19,41 @@ Keep these points in mind: - Inspect `.modules/aspire.ts` after `aspire add` to see the refreshed API surface available to `apphost.ts`. - The local `tsconfig.json` often includes `.modules/**/*.ts` in its compilation scope. +## Scenario: I Need Browser Automation For A JavaScript Frontend + +Use this when the AppHost is `apphost.ts` and the frontend is a Vite app or another JavaScript app. Browser Automation is optimized for frontend applications, so attach it to the browser-served frontend resource rather than backend services. + +```bash +aspire add browsers +``` + +For Vite frontends, attach browser automation to the Vite resource: + +```typescript +const frontend = await builder + .addViteApp("frontend", "./frontend") + .withExternalHttpEndpoints() + .withBrowserAutomation(); +``` + +For generic JavaScript frontends that run from a package.json script, attach browser automation to the JavaScript app resource: + +```typescript +const frontend = await builder + .addJavaScriptApp("frontend", "./frontend", { runScriptName: "dev" }) + .withHttpEndpoint({ env: "PORT" }) + .withExternalHttpEndpoints() + .withBrowserAutomation(); +``` + +Keep these points in mind: + +- Run `aspire add browsers` first so `.modules/aspire.ts` includes `withBrowserAutomation`. +- Use `addViteApp` for Vite projects and `addJavaScriptApp` for package.json-script frontends such as Next.js, CRA, or custom dev servers. +- Add an HTTP endpoint before `withBrowserAutomation`; for generic JavaScript apps, prefer `.withHttpEndpoint({ env: "PORT" })` when the dev server can read `PORT`. +- Use `.withExternalHttpEndpoints()` when the user or an agent needs to open the frontend directly in a browser. +- After editing, restart with `aspire start --isolated` and use `aspire describe --format Json` to find the generated `-browser-automation` resource. + ## Scenario: `.modules/` Disappeared After A Pull, Clean, Or Branch Switch Use this when generated support files are missing or stale and the TypeScript AppHost needs to be restored. diff --git a/playground/BrowserTelemetry/BrowserTelemetry.AppHost/AppHost.cs b/playground/BrowserTelemetry/BrowserTelemetry.AppHost/AppHost.cs index 1d8410184cd..4067828ffed 100644 --- a/playground/BrowserTelemetry/BrowserTelemetry.AppHost/AppHost.cs +++ b/playground/BrowserTelemetry/BrowserTelemetry.AppHost/AppHost.cs @@ -1,13 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. var builder = DistributedApplication.CreateBuilder(args); builder.AddProject("web") .WithExternalHttpEndpoints() - .WithBrowserLogs(); + .WithBrowserAutomation(); #if !SKIP_DASHBOARD_REFERENCE // This project is only added in playground projects to support development/debugging @@ -21,4 +21,4 @@ builder.Build().Run(); -#pragma warning restore ASPIREBROWSERLOGS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/playground/BrowserTelemetry/BrowserTelemetry.Web/Pages/Index.cshtml b/playground/BrowserTelemetry/BrowserTelemetry.Web/Pages/Index.cshtml index 0d3222fe293..ebe74870e91 100644 --- a/playground/BrowserTelemetry/BrowserTelemetry.Web/Pages/Index.cshtml +++ b/playground/BrowserTelemetry/BrowserTelemetry.Web/Pages/Index.cshtml @@ -2,14 +2,14 @@
-

Browser logs demo

+

Browser automation demo

- Use the web-browser-logs resource in the dashboard to open a tracked browser session, then + Use the web-browser-automation resource in the dashboard to open a tracked browser session, then use the buttons below to emit browser-side console logs, network requests, and unhandled failures.

-
+
Waiting for browser interaction.
diff --git a/playground/BrowserTelemetry/BrowserTelemetry.Web/Scripts/index.js b/playground/BrowserTelemetry/BrowserTelemetry.Web/Scripts/index.js index 374630057b5..47f9ee3e2f0 100644 --- a/playground/BrowserTelemetry/BrowserTelemetry.Web/Scripts/index.js +++ b/playground/BrowserTelemetry/BrowserTelemetry.Web/Scripts/index.js @@ -131,7 +131,7 @@ function wireButton(id, callback) { } function setStatus(message) { - const status = document.getElementById('browser-log-status'); + const status = document.getElementById('browser-automation-status'); if (!status) { return; } diff --git a/src/Aspire.Hosting.Browsers/BrowserAutomationBuilderExtensions.cs b/src/Aspire.Hosting.Browsers/BrowserAutomationBuilderExtensions.cs new file mode 100644 index 00000000000..28fdecb9b40 --- /dev/null +++ b/src/Aspire.Hosting.Browsers/BrowserAutomationBuilderExtensions.cs @@ -0,0 +1,1814 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only + +using System.Collections.Immutable; +using Aspire.Hosting.Browsers.Resources; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Nodes; +using Aspire.Hosting.ApplicationModel; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Aspire.Hosting; + +/// +/// Extension methods for adding browser automation resources to browser-based application resources. +/// +[Experimental("ASPIREBROWSERAUTOMATION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] +public static class BrowserAutomationBuilderExtensions +{ + internal const string BrowserResourceType = "BrowserAutomation"; + internal const string BrowserAutomationConfigurationSectionName = "Aspire:Hosting:BrowserAutomation"; + internal const string LegacyBrowserLogsConfigurationSectionName = "Aspire:Hosting:BrowserLogs"; + internal const string BrowserConfigurationKey = "Browser"; + internal const string BrowserPropertyName = "Browser"; + internal const string BrowserExecutablePropertyName = "Browser executable"; + internal const string BrowserHostOwnershipPropertyName = "Browser host ownership"; + internal const string ProfileConfigurationKey = "Profile"; + internal const string ProfilePropertyName = "Profile"; + internal const string UserDataModeConfigurationKey = "UserDataMode"; + internal const string UserDataModePropertyName = "User data mode"; + internal const BrowserUserDataMode DefaultUserDataMode = BrowserConfiguration.DefaultUserDataMode; + internal const string TargetUrlPropertyName = "Target URL"; + internal const string ActiveSessionsPropertyName = "Active sessions"; + internal const string BrowserSessionsPropertyName = "Browser sessions"; + internal const string ActiveSessionCountPropertyName = "Active session count"; + internal const string TotalSessionsLaunchedPropertyName = "Total sessions launched"; + internal const string LastErrorPropertyName = "Last error"; + internal const string LastSessionPropertyName = "Last session"; + internal const string OpenTrackedBrowserCommandName = "open-tracked-browser"; + internal const string ConfigureTrackedBrowserCommandName = "configure-tracked-browser"; + internal const string InspectBrowserCommandName = "inspect-browser"; + internal const string GetCommandName = "get"; + internal const string IsCommandName = "is"; + internal const string FindCommandName = "find"; + internal const string HighlightCommandName = "highlight"; + internal const string EvaluateCommandName = "eval"; + internal const string CookiesCommandName = "cookies"; + internal const string StorageCommandName = "storage"; + internal const string StateCommandName = "state"; + internal const string CdpCommandName = "cdp"; + internal const string TabsCommandName = "tabs"; + internal const string FramesCommandName = "frames"; + internal const string DialogCommandName = "dialog"; + internal const string DownloadsCommandName = "downloads"; + internal const string UploadCommandName = "upload"; + internal const string BrowserUrlCommandName = "url"; + internal const string BackBrowserCommandName = "back"; + internal const string ForwardBrowserCommandName = "forward"; + internal const string ReloadBrowserCommandName = "reload"; + internal const string NavigateBrowserCommandName = "navigate-browser"; + internal const string ClickBrowserCommandName = "click-browser"; + internal const string DoubleClickBrowserCommandName = "dblclick-browser"; + internal const string FillBrowserCommandName = "fill-browser"; + internal const string CheckBrowserCommandName = "check-browser"; + internal const string UncheckBrowserCommandName = "uncheck-browser"; + internal const string FocusBrowserElementCommandName = "focus-browser-element"; + internal const string TypeBrowserTextCommandName = "type-browser-text"; + internal const string PressBrowserKeyCommandName = "press-browser-key"; + internal const string KeyDownBrowserCommandName = "keydown-browser"; + internal const string KeyUpBrowserCommandName = "keyup-browser"; + internal const string HoverBrowserElementCommandName = "hover-browser-element"; + internal const string SelectBrowserOptionCommandName = "select-browser-option"; + internal const string ScrollBrowserCommandName = "scroll-browser"; + internal const string ScrollIntoViewBrowserCommandName = "scroll-into-view-browser"; + internal const string MouseBrowserCommandName = "mouse"; + internal const string WaitCommandName = "wait"; + internal const string WaitForBrowserCommandName = "wait-for-browser"; + internal const string WaitForBrowserUrlCommandName = "wait-for-browser-url"; + internal const string WaitForBrowserLoadStateCommandName = "wait-for-browser-load-state"; + internal const string WaitForBrowserElementStateCommandName = "wait-for-browser-element-state"; + internal const string CaptureScreenshotCommandName = "capture-screenshot"; + internal const string CloseTrackedBrowserCommandName = "close-tracked-browser"; + private const int DefaultSnapshotMaxElements = 80; + private const int DefaultSnapshotMaxTextLength = 8_000; + private const int DefaultBrowserCommandTimeoutMilliseconds = 10_000; + private const int MinimumBrowserCommandTimeoutMilliseconds = 100; + private const int MaximumBrowserCommandTimeoutMilliseconds = 60_000; + private static readonly JsonSerializerOptions s_commandResultJsonOptions = new(JsonSerializerDefaults.Web) + { + WriteIndented = true + }; + + /// + /// Adds a child resource that can open the application's primary browser endpoint in a tracked browser session, + /// surface browser diagnostics, automate browser interactions, and capture screenshots. + /// + /// The type of resource being configured. + /// The resource builder. + /// + /// The browser to launch. When not specified, the tracked browser uses the configured value from + /// Aspire:Hosting:BrowserAutomation and otherwise prefers an installed "msedge" browser in shared user data + /// mode, an installed "chrome" browser in isolated user data mode, and finally falls back to "chrome". + /// Supported values include logical + /// browser names such as "msedge" and "chrome", or an explicit browser executable path. + /// + /// + /// Optional Chromium profile name or directory name to use. Only valid when the effective user data mode + /// is . When not specified, the tracked browser uses the + /// configured value from Aspire:Hosting:BrowserAutomation if present. + /// + /// + /// Optional that selects whether the tracked browser launches against + /// a persistent Aspire-managed user data directory shared across all AppHosts on the machine + /// (, the default) or a per-AppHost persistent user data directory + /// (). Both modes use Aspire-managed paths under + /// %LocalAppData%\Aspire\BrowserData on Windows (or platform equivalents); the user's normal browser + /// profile is never used. When not specified, the tracked browser uses the configured value from + /// Aspire:Hosting:BrowserAutomation and otherwise defaults to . + /// + /// A reference to the original for further chaining. + /// + /// + /// This method adds a child browser automation resource beneath the parent resource represented by . + /// The child resource exposes dashboard commands that launch a Chromium-based browser in a tracked mode, attach to + /// the browser's debugging protocol, forward browser console, error, exception, and network output to the child + /// resource's console log stream, automate browser interactions, and capture screenshots as command artifacts. + /// + /// + /// The tracked browser session uses the Chrome DevTools + /// Protocol (CDP) to subscribe to browser runtime, log, page, and network events. + /// + /// + /// The parent resource must expose at least one HTTP or HTTPS endpoint. HTTPS endpoints are preferred over HTTP + /// endpoints when selecting the browser target URL. + /// + /// + /// Browser, profile, and user data mode settings can also be supplied from configuration + /// using Aspire:Hosting:BrowserAutomation:Browser, Aspire:Hosting:BrowserAutomation:Profile, + /// and Aspire:Hosting:BrowserAutomation:UserDataMode, or scoped to a specific resource with + /// Aspire:Hosting:BrowserAutomation:{ResourceName}:Browser, + /// Aspire:Hosting:BrowserAutomation:{ResourceName}:Profile, and + /// Aspire:Hosting:BrowserAutomation:{ResourceName}:UserDataMode. Explicit method arguments override configuration. + /// + /// + /// + /// Add browser automation for a web front end: + /// + /// var builder = DistributedApplication.CreateBuilder(args); + /// + /// builder.AddProject<Projects.WebFrontend>("web") + /// .WithExternalHttpEndpoints() + /// .WithBrowserAutomation(); + /// + /// + [Experimental("ASPIREBROWSERAUTOMATION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] + [AspireExport(Description = "Adds a child browser automation resource that opens tracked browser sessions, captures browser diagnostics, automates browser interactions, and captures screenshots.")] + public static IResourceBuilder WithBrowserAutomation( + this IResourceBuilder builder, + string? browser = null, + string? profile = null, + BrowserUserDataMode? userDataMode = null) + where T : IResourceWithEndpoints + { + ArgumentNullException.ThrowIfNull(builder); + ThrowIfBlankWhenSpecified(browser, nameof(browser)); + ThrowIfBlankWhenSpecified(profile, nameof(profile)); + + builder.ApplicationBuilder.Services.TryAddSingleton(); + builder.ApplicationBuilder.Services.TryAddSingleton(); + builder.ApplicationBuilder.Services.TryAddSingleton(); + + var parentResource = builder.Resource; + var explicitConfigurationValues = new BrowserConfigurationExplicitValues(browser, profile, userDataMode); + var initialConfiguration = BrowserConfiguration.Resolve(builder.ApplicationBuilder.Configuration, parentResource.Name, explicitConfigurationValues); + var browserAutomationResource = new BrowserAutomationResource( + $"{parentResource.Name}-browser-automation", + parentResource, + initialConfiguration, + explicitConfigurationValues); + browserAutomationResource.Annotations.Add(NameValidationPolicyAnnotation.None); + + builder.ApplicationBuilder.AddResource(browserAutomationResource) + .WithParentRelationship(parentResource) + .ExcludeFromManifest() + .WithIconName("GlobeDesktop") + .WithInitialState(new CustomResourceSnapshot + { + ResourceType = BrowserResourceType, + CreationTimeStamp = DateTime.UtcNow, + State = KnownResourceStates.NotStarted, + Properties = CreateInitialProperties(parentResource.Name, initialConfiguration) + }) + .WithCommand( + OpenTrackedBrowserCommandName, + BrowserCommandStrings.OpenTrackedBrowserName, + async context => + { + try + { + var configuration = context.ServiceProvider.GetRequiredService(); + var configurationStore = context.ServiceProvider.GetRequiredService(); + var currentConfiguration = browserAutomationResource.ResolveCurrentConfiguration(configuration, configurationStore); + var url = ResolveBrowserUrl(parentResource); + var sessionManager = context.ServiceProvider.GetRequiredService(); + await sessionManager.StartSessionAsync(browserAutomationResource, currentConfiguration, context.ResourceName, url, context.CancellationToken).ConfigureAwait(false); + return CommandResults.Success(); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + new CommandOptions + { + Description = BrowserCommandStrings.OpenTrackedBrowserDescription, + IconName = "Open", + IconVariant = IconVariant.Regular, + IsHighlighted = true, + UpdateState = context => + { + var childState = context.ResourceSnapshot.State?.Text; + if (childState == KnownResourceStates.Starting) + { + return ResourceCommandState.Disabled; + } + + var resourceNotifications = context.ServiceProvider.GetRequiredService(); + if (resourceNotifications.TryGetCurrentState(parentResource.Name, out var resourceEvent)) + { + var parentState = resourceEvent.Snapshot.State?.Text; + if (parentState == KnownResourceStates.Running || parentState == KnownResourceStates.RuntimeUnhealthy) + { + return ResourceCommandState.Enabled; + } + } + + return ResourceCommandState.Disabled; + } + }) + .WithCommand( + ConfigureTrackedBrowserCommandName, + BrowserCommandStrings.ConfigureTrackedBrowserName, + async context => + { + try + { + var configurationManager = context.ServiceProvider.GetRequiredService(); + return await configurationManager.ConfigureAsync(browserAutomationResource, context.CancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + new CommandOptions + { + Description = BrowserCommandStrings.ConfigureTrackedBrowserDescription, + IconName = "Settings", + IconVariant = IconVariant.Regular, + UpdateState = context => + { + var interactionService = context.ServiceProvider.GetRequiredService(); + return interactionService.IsAvailable + ? ResourceCommandState.Enabled + : ResourceCommandState.Disabled; + } + }) + .WithCommand( + InspectBrowserCommandName, + BrowserCommandStrings.InspectBrowserName, + async context => + { + try + { + var sessionManager = context.ServiceProvider.GetRequiredService(); + var maxElements = GetOptionalIntegerArgument(context.Arguments, "maxElements", DefaultSnapshotMaxElements, 1, 500); + var maxTextLength = GetOptionalIntegerArgument(context.Arguments, "maxTextLength", DefaultSnapshotMaxTextLength, 100, 50_000); + var resultJson = await sessionManager.GetPageSnapshotAsync(context.ResourceName, maxElements, maxTextLength, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.InspectBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.InspectBrowserDescription, + "DocumentSearch", + [ + CreateNumberArgument("maxElements", BrowserCommandStrings.MaxElementsArgumentLabel, BrowserCommandStrings.MaxElementsArgumentDescription, DefaultSnapshotMaxElements.ToString(CultureInfo.InvariantCulture), required: false), + CreateNumberArgument("maxTextLength", BrowserCommandStrings.MaxTextLengthArgumentLabel, BrowserCommandStrings.MaxTextLengthArgumentDescription, DefaultSnapshotMaxTextLength.ToString(CultureInfo.InvariantCulture), required: false) + ])) + .WithCommand( + GetCommandName, + BrowserCommandStrings.GetBrowserName, + async context => + { + try + { + var property = GetRequiredStringArgument(context.Arguments, "property"); + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var name = GetOptionalStringArgument(context.Arguments, "name"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.GetAsync(context.ResourceName, property, selector, name, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.GetBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.GetBrowserDescription, + "Code", + [ + CreateChoiceArgument("property", BrowserCommandStrings.PropertyArgumentLabel, BrowserCommandStrings.PropertyArgumentDescription, "text", required: true, ["title", "url", "text", "html", "value", "attr", "count", "box", "styles"]), + CreateSelectorArgument(required: false), + CreateTextArgument("name", BrowserCommandStrings.NameArgumentLabel, BrowserCommandStrings.NameArgumentDescription, required: false, placeholder: "href") + ])) + .WithCommand( + IsCommandName, + BrowserCommandStrings.IsBrowserName, + async context => + { + try + { + var state = GetRequiredStringArgument(context.Arguments, "state"); + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.IsAsync(context.ResourceName, state, selector, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.IsBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.IsBrowserDescription, + "CheckmarkCircle", + [ + CreateChoiceArgument("state", BrowserCommandStrings.StateArgumentLabel, BrowserCommandStrings.StateArgumentDescription, "visible", required: true, ["visible", "enabled", "checked"]), + CreateSelectorArgument() + ])) + .WithCommand( + FindCommandName, + BrowserCommandStrings.FindBrowserName, + async context => + { + try + { + var kind = GetRequiredStringArgument(context.Arguments, "kind"); + var value = GetRequiredStringArgument(context.Arguments, "value"); + var name = GetOptionalStringArgument(context.Arguments, "name"); + var index = GetOptionalIntegerArgument(context.Arguments, "index", defaultValue: 1, minimum: 1, maximum: 10_000); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.FindAsync(context.ResourceName, kind, value, name, index, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.FindBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.FindBrowserDescription, + "Search", + [ + CreateChoiceArgument("kind", BrowserCommandStrings.KindArgumentLabel, BrowserCommandStrings.KindArgumentDescription, "text", required: true, ["role", "text", "label", "placeholder", "alt", "title", "testid", "first", "last", "nth"]), + CreateTextArgument("value", BrowserCommandStrings.FindValueArgumentLabel, BrowserCommandStrings.FindValueArgumentDescription, required: true, placeholder: "Submit"), + CreateTextArgument("name", BrowserCommandStrings.NameArgumentLabel, BrowserCommandStrings.NameArgumentDescription, required: false, placeholder: "Save"), + CreateNumberArgument("index", BrowserCommandStrings.IndexArgumentLabel, BrowserCommandStrings.IndexArgumentDescription, "1", required: false) + ])) + .WithCommand( + HighlightCommandName, + BrowserCommandStrings.HighlightBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.HighlightAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.HighlightBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.HighlightBrowserDescription, + "Highlight", + [CreateSelectorArgument()])) + .WithCommand( + EvaluateCommandName, + BrowserCommandStrings.EvaluateBrowserName, + async context => + { + try + { + var expression = GetRequiredStringArgument(context.Arguments, "expression"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.EvaluateAsync(context.ResourceName, expression, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.EvaluateBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.EvaluateBrowserDescription, + "DeveloperBoard", + [CreateTextArgument("expression", BrowserCommandStrings.ExpressionArgumentLabel, BrowserCommandStrings.ExpressionArgumentDescription, required: true, placeholder: "document.title")])) + .WithCommand( + CookiesCommandName, + BrowserCommandStrings.CookiesBrowserName, + async context => + { + try + { + var action = GetRequiredStringArgument(context.Arguments, "action"); + var name = GetOptionalStringArgument(context.Arguments, "name"); + var value = action == "set" ? GetRequiredStringArgument(context.Arguments, "value", allowEmpty: true) : GetOptionalStringArgument(context.Arguments, "value"); + var domain = GetOptionalStringArgument(context.Arguments, "domain"); + var path = GetOptionalStringArgument(context.Arguments, "path"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.CookiesAsync(context.ResourceName, action, name, value, domain, path, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.CookiesBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.CookiesBrowserDescription, + "Cookies", + [ + CreateChoiceArgument("action", BrowserCommandStrings.ActionArgumentLabel, BrowserCommandStrings.CookiesActionArgumentDescription, "get", required: true, ["get", "set", "clear"]), + CreateTextArgument("name", BrowserCommandStrings.CookieNameArgumentLabel, BrowserCommandStrings.CookieNameArgumentDescription, required: false, placeholder: "session_id"), + CreateTextArgument("value", BrowserCommandStrings.CookieValueArgumentLabel, BrowserCommandStrings.CookieValueArgumentDescription, required: false), + CreateTextArgument("domain", BrowserCommandStrings.CookieDomainArgumentLabel, BrowserCommandStrings.CookieDomainArgumentDescription, required: false, placeholder: "example.com"), + CreateTextArgument("path", BrowserCommandStrings.CookiePathArgumentLabel, BrowserCommandStrings.CookiePathArgumentDescription, required: false, placeholder: "/") + ])) + .WithCommand( + StorageCommandName, + BrowserCommandStrings.StorageBrowserName, + async context => + { + try + { + var area = GetRequiredStringArgument(context.Arguments, "area"); + var action = GetRequiredStringArgument(context.Arguments, "action"); + var key = GetOptionalStringArgument(context.Arguments, "key"); + var value = action == "set" ? GetRequiredStringArgument(context.Arguments, "value", allowEmpty: true) : GetOptionalStringArgument(context.Arguments, "value"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.StorageAsync(context.ResourceName, area, action, key, value, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.StorageBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.StorageBrowserDescription, + "Database", + [ + CreateChoiceArgument("area", BrowserCommandStrings.StorageAreaArgumentLabel, BrowserCommandStrings.StorageAreaArgumentDescription, "local", required: true, ["local", "session"]), + CreateChoiceArgument("action", BrowserCommandStrings.ActionArgumentLabel, BrowserCommandStrings.StorageActionArgumentDescription, "get", required: true, ["get", "set", "clear"]), + CreateTextArgument("key", BrowserCommandStrings.StorageKeyArgumentLabel, BrowserCommandStrings.StorageKeyArgumentDescription, required: false, placeholder: "theme"), + CreateTextArgument("value", BrowserCommandStrings.StorageValueArgumentLabel, BrowserCommandStrings.StorageValueArgumentDescription, required: false) + ])) + .WithCommand( + StateCommandName, + BrowserCommandStrings.StateBrowserName, + async context => + { + try + { + var action = GetRequiredStringArgument(context.Arguments, "action"); + var state = action == "set" ? GetRequiredStringArgument(context.Arguments, "state") : GetOptionalStringArgument(context.Arguments, "state"); + var clearExisting = GetOptionalBooleanArgument(context.Arguments, "clearExisting", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.StateAsync(context.ResourceName, action, state, clearExisting, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.StateBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.StateBrowserDescription, + "Save", + [ + CreateChoiceArgument("action", BrowserCommandStrings.ActionArgumentLabel, BrowserCommandStrings.StateActionArgumentDescription, "get", required: true, ["get", "set", "clear"]), + CreateTextArgument("state", BrowserCommandStrings.StateJsonArgumentLabel, BrowserCommandStrings.StateJsonArgumentDescription, required: false, placeholder: """{"cookies":[],"localStorage":{},"sessionStorage":{}}"""), + CreateBooleanArgument("clearExisting", BrowserCommandStrings.ClearExistingArgumentLabel, BrowserCommandStrings.ClearExistingArgumentDescription, value: "false", required: false) + ])) + .WithCommand( + CdpCommandName, + BrowserCommandStrings.CdpBrowserName, + async context => + { + try + { + var method = GetRequiredStringArgument(context.Arguments, "method"); + var parametersJson = GetOptionalStringArgument(context.Arguments, "params"); + var session = GetOptionalStringArgument(context.Arguments, "session") ?? "page"; + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.CdpAsync(context.ResourceName, method, parametersJson, session, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.CdpBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.CdpBrowserDescription, + "DeveloperBoard", + [ + CreateTextArgument("method", BrowserCommandStrings.CdpMethodArgumentLabel, BrowserCommandStrings.CdpMethodArgumentDescription, required: true, placeholder: "Runtime.evaluate"), + CreateTextArgument("params", BrowserCommandStrings.CdpParamsArgumentLabel, BrowserCommandStrings.CdpParamsArgumentDescription, required: false, placeholder: """{"expression":"document.title","returnByValue":true}"""), + CreateChoiceArgument("session", BrowserCommandStrings.CdpSessionArgumentLabel, BrowserCommandStrings.CdpSessionArgumentDescription, "page", required: false, ["page", "browser"]) + ])) + .WithCommand( + TabsCommandName, + BrowserCommandStrings.TabsBrowserName, + async context => + { + try + { + var action = GetRequiredStringArgument(context.Arguments, "action"); + var url = GetOptionalStringArgument(context.Arguments, "url"); + var targetId = GetOptionalStringArgument(context.Arguments, "targetId"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.TabsAsync(context.ResourceName, action, url, targetId, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.TabsBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.TabsBrowserDescription, + "Tab", + [ + CreateChoiceArgument("action", BrowserCommandStrings.ActionArgumentLabel, BrowserCommandStrings.TabsActionArgumentDescription, "list", required: true, ["list", "open", "close"]), + CreateTextArgument("url", BrowserCommandStrings.UrlArgumentLabel, BrowserCommandStrings.TabUrlArgumentDescription, required: false, placeholder: "https://example.com/"), + CreateTextArgument("targetId", BrowserCommandStrings.TargetIdArgumentLabel, BrowserCommandStrings.TargetIdArgumentDescription, required: false, placeholder: "target-id") + ])) + .WithCommand( + FramesCommandName, + BrowserCommandStrings.FramesBrowserName, + async context => + { + try + { + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.FramesAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.FramesBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions(BrowserCommandStrings.FramesBrowserDescription, "Window", [])) + .WithCommand( + DialogCommandName, + BrowserCommandStrings.DialogBrowserName, + async context => + { + try + { + var action = GetRequiredStringArgument(context.Arguments, "action"); + var promptText = GetOptionalStringArgument(context.Arguments, "promptText"); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.DialogAsync(context.ResourceName, action, promptText, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.DialogBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.DialogBrowserDescription, + "WindowAd", + [ + CreateChoiceArgument("action", BrowserCommandStrings.ActionArgumentLabel, BrowserCommandStrings.DialogActionArgumentDescription, "accept", required: true, ["accept", "dismiss"]), + CreateTextArgument("promptText", BrowserCommandStrings.PromptTextArgumentLabel, BrowserCommandStrings.PromptTextArgumentDescription, required: false) + ])) + .WithCommand( + DownloadsCommandName, + BrowserCommandStrings.DownloadsBrowserName, + async context => + { + try + { + var behavior = GetRequiredStringArgument(context.Arguments, "behavior"); + var downloadPath = GetOptionalStringArgument(context.Arguments, "downloadPath"); + var eventsEnabled = GetOptionalBooleanArgument(context.Arguments, "eventsEnabled", defaultValue: true); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.DownloadsAsync(context.ResourceName, behavior, downloadPath, eventsEnabled, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.DownloadsBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.DownloadsBrowserDescription, + "ArrowDownload", + [ + CreateChoiceArgument("behavior", BrowserCommandStrings.DownloadBehaviorArgumentLabel, BrowserCommandStrings.DownloadBehaviorArgumentDescription, "allow", required: true, ["allow", "allowAndName", "deny", "default"]), + CreateTextArgument("downloadPath", BrowserCommandStrings.DownloadPathArgumentLabel, BrowserCommandStrings.DownloadPathArgumentDescription, required: false), + CreateBooleanArgument("eventsEnabled", BrowserCommandStrings.DownloadEventsEnabledArgumentLabel, BrowserCommandStrings.DownloadEventsEnabledArgumentDescription, value: "true", required: false) + ])) + .WithCommand( + UploadCommandName, + BrowserCommandStrings.UploadBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var files = GetRequiredStringArgument(context.Arguments, "files"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.UploadAsync(context.ResourceName, selector, files, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.UploadBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.UploadBrowserDescription, + "Attach", + [ + CreateSelectorArgument(), + CreateTextArgument("files", BrowserCommandStrings.FilesArgumentLabel, BrowserCommandStrings.FilesArgumentDescription, required: true, placeholder: """["/tmp/file.txt"]"""), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + BrowserUrlCommandName, + BrowserCommandStrings.BrowserUrlName, + async context => + { + try + { + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.GetUrlAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.BrowserUrlSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions(BrowserCommandStrings.BrowserUrlDescription, "Link", [])) + .WithCommand( + BackBrowserCommandName, + BrowserCommandStrings.BackBrowserName, + async context => + { + try + { + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.GoBackAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.BackBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions(BrowserCommandStrings.BackBrowserDescription, "ArrowLeft", [CreateSnapshotAfterArgument()])) + .WithCommand( + ForwardBrowserCommandName, + BrowserCommandStrings.ForwardBrowserName, + async context => + { + try + { + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.GoForwardAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.ForwardBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions(BrowserCommandStrings.ForwardBrowserDescription, "ArrowRight", [CreateSnapshotAfterArgument()])) + .WithCommand( + ReloadBrowserCommandName, + BrowserCommandStrings.ReloadBrowserName, + async context => + { + try + { + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.ReloadAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.ReloadBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions(BrowserCommandStrings.ReloadBrowserDescription, "ArrowClockwise", [CreateSnapshotAfterArgument()])) + .WithCommand( + NavigateBrowserCommandName, + BrowserCommandStrings.NavigateBrowserName, + async context => + { + try + { + var urlText = GetRequiredStringArgument(context.Arguments, "url"); + if (!Uri.TryCreate(urlText, UriKind.Absolute, out var url)) + { + throw new InvalidOperationException("The browser navigation URL must be an absolute URI."); + } + + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.NavigateAsync(browserAutomationResource, context.ResourceName, url, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.NavigateBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.NavigateBrowserDescription, + "Navigation", + [ + CreateTextArgument("url", BrowserCommandStrings.UrlArgumentLabel, BrowserCommandStrings.UrlArgumentDescription, required: true, placeholder: "https://example.com/"), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + ClickBrowserCommandName, + BrowserCommandStrings.ClickBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.ClickAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.ClickBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.ClickBrowserDescription, + "CursorClick", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + DoubleClickBrowserCommandName, + BrowserCommandStrings.DoubleClickBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.DoubleClickAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.DoubleClickBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.DoubleClickBrowserDescription, + "CursorClick", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + FillBrowserCommandName, + BrowserCommandStrings.FillBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var value = GetRequiredStringArgument(context.Arguments, "value", allowEmpty: true); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.FillAsync(context.ResourceName, selector, value, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.FillBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.FillBrowserDescription, + "TextEdit", + [ + CreateSelectorArgument(), + CreateTextArgument("value", BrowserCommandStrings.ValueArgumentLabel, BrowserCommandStrings.ValueArgumentDescription, required: true), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + CheckBrowserCommandName, + BrowserCommandStrings.CheckBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.CheckAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.CheckBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.CheckBrowserDescription, + "CheckboxChecked", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + UncheckBrowserCommandName, + BrowserCommandStrings.UncheckBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.UncheckAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.UncheckBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.UncheckBrowserDescription, + "CheckboxUnchecked", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + FocusBrowserElementCommandName, + BrowserCommandStrings.FocusBrowserElementName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.FocusAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.FocusBrowserElementSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.FocusBrowserElementDescription, + "Cursor", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + TypeBrowserTextCommandName, + BrowserCommandStrings.TypeBrowserTextName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var text = GetRequiredStringArgument(context.Arguments, "text", allowEmpty: true); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.TypeAsync(context.ResourceName, selector, text, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.TypeBrowserTextSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.TypeBrowserTextDescription, + "TextEdit", + [ + CreateSelectorArgument(), + CreateTextArgument("text", BrowserCommandStrings.TextArgumentLabel, BrowserCommandStrings.TypeTextArgumentDescription, required: true), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + PressBrowserKeyCommandName, + BrowserCommandStrings.PressBrowserKeyName, + async context => + { + try + { + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var key = GetRequiredStringArgument(context.Arguments, "key"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.PressAsync(context.ResourceName, selector, key, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.PressBrowserKeySucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.PressBrowserKeyDescription, + "Keyboard", + [ + CreateTextArgument("key", BrowserCommandStrings.KeyArgumentLabel, BrowserCommandStrings.KeyArgumentDescription, required: true, placeholder: "Enter"), + CreateSelectorArgument(required: false), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + KeyDownBrowserCommandName, + BrowserCommandStrings.KeyDownBrowserName, + async context => + { + try + { + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var key = GetRequiredStringArgument(context.Arguments, "key"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.KeyDownAsync(context.ResourceName, selector, key, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.KeyDownBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.KeyDownBrowserDescription, + "Keyboard", + [ + CreateTextArgument("key", BrowserCommandStrings.KeyArgumentLabel, BrowserCommandStrings.KeyArgumentDescription, required: true, placeholder: "Shift"), + CreateSelectorArgument(required: false), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + KeyUpBrowserCommandName, + BrowserCommandStrings.KeyUpBrowserName, + async context => + { + try + { + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var key = GetRequiredStringArgument(context.Arguments, "key"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.KeyUpAsync(context.ResourceName, selector, key, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.KeyUpBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.KeyUpBrowserDescription, + "Keyboard", + [ + CreateTextArgument("key", BrowserCommandStrings.KeyArgumentLabel, BrowserCommandStrings.KeyArgumentDescription, required: true, placeholder: "Shift"), + CreateSelectorArgument(required: false), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + HoverBrowserElementCommandName, + BrowserCommandStrings.HoverBrowserElementName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.HoverAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.HoverBrowserElementSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.HoverBrowserElementDescription, + "CursorHover", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + SelectBrowserOptionCommandName, + BrowserCommandStrings.SelectBrowserOptionName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var value = GetRequiredStringArgument(context.Arguments, "value", allowEmpty: true); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.SelectAsync(context.ResourceName, selector, value, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.SelectBrowserOptionSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.SelectBrowserOptionDescription, + "CheckboxChecked", + [ + CreateSelectorArgument(), + CreateTextArgument("value", BrowserCommandStrings.ValueArgumentLabel, BrowserCommandStrings.SelectValueArgumentDescription, required: true), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + ScrollBrowserCommandName, + BrowserCommandStrings.ScrollBrowserName, + async context => + { + try + { + var deltaY = GetOptionalIntegerArgument(context.Arguments, "deltaY", 600, -100_000, 100_000); + var deltaX = GetOptionalIntegerArgument(context.Arguments, "deltaX", 0, -100_000, 100_000); + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.ScrollAsync(context.ResourceName, selector, deltaX, deltaY, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.ScrollBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.ScrollBrowserDescription, + "ArrowDown", + [ + CreateNumberArgument("deltaY", BrowserCommandStrings.DeltaYArgumentLabel, BrowserCommandStrings.DeltaYArgumentDescription, "600", required: false), + CreateNumberArgument("deltaX", BrowserCommandStrings.DeltaXArgumentLabel, BrowserCommandStrings.DeltaXArgumentDescription, "0", required: false), + CreateSelectorArgument(required: false), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + ScrollIntoViewBrowserCommandName, + BrowserCommandStrings.ScrollIntoViewBrowserName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.ScrollIntoViewAsync(context.ResourceName, selector, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.ScrollIntoViewBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.ScrollIntoViewBrowserDescription, + "ArrowDown", + [CreateSelectorArgument(), CreateSnapshotAfterArgument()])) + .WithCommand( + MouseBrowserCommandName, + BrowserCommandStrings.MouseBrowserName, + async context => + { + try + { + var action = GetRequiredStringArgument(context.Arguments, "action"); + var x = GetOptionalIntegerArgument(context.Arguments, "x", 0, -100_000, 100_000); + var y = GetOptionalIntegerArgument(context.Arguments, "y", 0, -100_000, 100_000); + var button = GetOptionalStringArgument(context.Arguments, "button"); + var deltaX = GetOptionalIntegerArgument(context.Arguments, "deltaX", 0, -100_000, 100_000); + var deltaY = GetOptionalIntegerArgument(context.Arguments, "deltaY", 0, -100_000, 100_000); + var snapshotAfter = GetOptionalBooleanArgument(context.Arguments, "snapshotAfter", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.MouseAsync(context.ResourceName, action, x, y, button, deltaX, deltaY, context.CancellationToken).ConfigureAwait(false); + resultJson = await AddSnapshotAfterAsync(sessionManager, context.ResourceName, resultJson, snapshotAfter, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.MouseBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.MouseBrowserDescription, + "Cursor", + [ + CreateChoiceArgument("action", BrowserCommandStrings.ActionArgumentLabel, BrowserCommandStrings.MouseActionArgumentDescription, "move", required: true, ["move", "down", "up", "click", "wheel"]), + CreateNumberArgument("x", BrowserCommandStrings.XCoordinateArgumentLabel, BrowserCommandStrings.XCoordinateArgumentDescription, "0", required: false), + CreateNumberArgument("y", BrowserCommandStrings.YCoordinateArgumentLabel, BrowserCommandStrings.YCoordinateArgumentDescription, "0", required: false), + CreateChoiceArgument("button", BrowserCommandStrings.MouseButtonArgumentLabel, BrowserCommandStrings.MouseButtonArgumentDescription, "left", required: false, ["left", "middle", "right"]), + CreateNumberArgument("deltaX", BrowserCommandStrings.DeltaXArgumentLabel, BrowserCommandStrings.DeltaXArgumentDescription, "0", required: false), + CreateNumberArgument("deltaY", BrowserCommandStrings.DeltaYArgumentLabel, BrowserCommandStrings.DeltaYArgumentDescription, "0", required: false), + CreateSnapshotAfterArgument() + ])) + .WithCommand( + WaitForBrowserCommandName, + BrowserCommandStrings.WaitForBrowserName, + async context => + { + try + { + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var text = GetOptionalStringArgument(context.Arguments, "text"); + var timeoutMilliseconds = GetOptionalIntegerArgument(context.Arguments, "timeoutMilliseconds", DefaultBrowserCommandTimeoutMilliseconds, MinimumBrowserCommandTimeoutMilliseconds, MaximumBrowserCommandTimeoutMilliseconds); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.WaitForAsync(context.ResourceName, selector, text, timeoutMilliseconds, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.WaitForBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.WaitForBrowserDescription, + "Timer", + [ + CreateSelectorArgument(required: false), + CreateTextArgument("text", BrowserCommandStrings.TextArgumentLabel, BrowserCommandStrings.TextArgumentDescription, required: false), + CreateNumberArgument("timeoutMilliseconds", BrowserCommandStrings.TimeoutMillisecondsArgumentLabel, BrowserCommandStrings.TimeoutMillisecondsArgumentDescription, DefaultBrowserCommandTimeoutMilliseconds.ToString(CultureInfo.InvariantCulture), required: false) + ])) + .WithCommand( + WaitCommandName, + BrowserCommandStrings.WaitBrowserName, + async context => + { + try + { + var selector = GetOptionalStringArgument(context.Arguments, "selector"); + var text = GetOptionalStringArgument(context.Arguments, "text"); + var urlContains = GetOptionalStringArgument(context.Arguments, "urlContains"); + var url = GetOptionalStringArgument(context.Arguments, "url"); + var match = GetOptionalStringArgument(context.Arguments, "match") ?? "contains"; + var loadState = GetOptionalStringArgument(context.Arguments, "loadState"); + var elementState = GetOptionalStringArgument(context.Arguments, "elementState"); + var function = GetOptionalStringArgument(context.Arguments, "function"); + var timeoutMilliseconds = GetOptionalIntegerArgument(context.Arguments, "timeoutMilliseconds", DefaultBrowserCommandTimeoutMilliseconds, MinimumBrowserCommandTimeoutMilliseconds, MaximumBrowserCommandTimeoutMilliseconds); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await ExecuteUnifiedWaitAsync( + sessionManager, + context.ResourceName, + selector, + text, + urlContains, + url, + match, + loadState, + elementState, + function, + timeoutMilliseconds, + context.CancellationToken).ConfigureAwait(false); + + return BrowserJsonCommandSuccess(BrowserCommandStrings.WaitBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.WaitBrowserDescription, + "Timer", + [ + CreateSelectorArgument(required: false), + CreateTextArgument("text", BrowserCommandStrings.TextArgumentLabel, BrowserCommandStrings.TextArgumentDescription, required: false), + CreateTextArgument("urlContains", BrowserCommandStrings.UrlContainsArgumentLabel, BrowserCommandStrings.UrlContainsArgumentDescription, required: false, placeholder: "/dashboard"), + CreateTextArgument("url", BrowserCommandStrings.UrlArgumentLabel, BrowserCommandStrings.WaitUrlArgumentDescription, required: false, placeholder: "/orders"), + CreateChoiceArgument("match", BrowserCommandStrings.MatchArgumentLabel, BrowserCommandStrings.MatchArgumentDescription, "contains", required: false, ["contains", "exact", "regex"]), + CreateChoiceArgument("loadState", BrowserCommandStrings.LoadStateArgumentLabel, BrowserCommandStrings.LoadStateArgumentDescription, value: null, required: false, ["domcontentloaded", "load", "complete", "networkidle"]), + CreateChoiceArgument("elementState", BrowserCommandStrings.ElementStateArgumentLabel, BrowserCommandStrings.ElementStateArgumentDescription, value: null, required: false, ["attached", "detached", "visible", "hidden", "enabled", "disabled", "checked", "unchecked"]), + CreateTextArgument("function", BrowserCommandStrings.FunctionArgumentLabel, BrowserCommandStrings.FunctionArgumentDescription, required: false, placeholder: "window.__appReady === true"), + CreateNumberArgument("timeoutMilliseconds", BrowserCommandStrings.TimeoutMillisecondsArgumentLabel, BrowserCommandStrings.TimeoutMillisecondsArgumentDescription, DefaultBrowserCommandTimeoutMilliseconds.ToString(CultureInfo.InvariantCulture), required: false) + ])) + .WithCommand( + WaitForBrowserUrlCommandName, + BrowserCommandStrings.WaitForBrowserUrlName, + async context => + { + try + { + var url = GetRequiredStringArgument(context.Arguments, "url"); + var match = GetOptionalStringArgument(context.Arguments, "match") ?? "contains"; + var timeoutMilliseconds = GetOptionalIntegerArgument(context.Arguments, "timeoutMilliseconds", DefaultBrowserCommandTimeoutMilliseconds, MinimumBrowserCommandTimeoutMilliseconds, MaximumBrowserCommandTimeoutMilliseconds); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.WaitForUrlAsync(context.ResourceName, url, match, timeoutMilliseconds, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.WaitForBrowserUrlSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.WaitForBrowserUrlDescription, + "Link", + [ + CreateTextArgument("url", BrowserCommandStrings.UrlArgumentLabel, BrowserCommandStrings.WaitUrlArgumentDescription, required: true, placeholder: "/orders"), + CreateChoiceArgument("match", BrowserCommandStrings.MatchArgumentLabel, BrowserCommandStrings.MatchArgumentDescription, "contains", required: false, ["contains", "exact", "regex"]), + CreateNumberArgument("timeoutMilliseconds", BrowserCommandStrings.TimeoutMillisecondsArgumentLabel, BrowserCommandStrings.TimeoutMillisecondsArgumentDescription, DefaultBrowserCommandTimeoutMilliseconds.ToString(CultureInfo.InvariantCulture), required: false) + ])) + .WithCommand( + WaitForBrowserLoadStateCommandName, + BrowserCommandStrings.WaitForBrowserLoadStateName, + async context => + { + try + { + var state = GetOptionalStringArgument(context.Arguments, "state") ?? "load"; + var timeoutMilliseconds = GetOptionalIntegerArgument(context.Arguments, "timeoutMilliseconds", DefaultBrowserCommandTimeoutMilliseconds, MinimumBrowserCommandTimeoutMilliseconds, MaximumBrowserCommandTimeoutMilliseconds); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.WaitForLoadStateAsync(context.ResourceName, state, timeoutMilliseconds, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.WaitForBrowserLoadStateSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.WaitForBrowserLoadStateDescription, + "ArrowSync", + [ + CreateChoiceArgument("state", BrowserCommandStrings.StateArgumentLabel, BrowserCommandStrings.LoadStateArgumentDescription, "load", required: false, ["domcontentloaded", "load", "networkidle"]), + CreateNumberArgument("timeoutMilliseconds", BrowserCommandStrings.TimeoutMillisecondsArgumentLabel, BrowserCommandStrings.TimeoutMillisecondsArgumentDescription, DefaultBrowserCommandTimeoutMilliseconds.ToString(CultureInfo.InvariantCulture), required: false) + ])) + .WithCommand( + WaitForBrowserElementStateCommandName, + BrowserCommandStrings.WaitForBrowserElementStateName, + async context => + { + try + { + var selector = GetRequiredStringArgument(context.Arguments, "selector"); + var state = GetOptionalStringArgument(context.Arguments, "state") ?? "visible"; + var timeoutMilliseconds = GetOptionalIntegerArgument(context.Arguments, "timeoutMilliseconds", DefaultBrowserCommandTimeoutMilliseconds, MinimumBrowserCommandTimeoutMilliseconds, MaximumBrowserCommandTimeoutMilliseconds); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.WaitForElementStateAsync(context.ResourceName, selector, state, timeoutMilliseconds, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.WaitForBrowserElementStateSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + CreateBrowserCommandOptions( + BrowserCommandStrings.WaitForBrowserElementStateDescription, + "Timer", + [ + CreateSelectorArgument(), + CreateChoiceArgument("state", BrowserCommandStrings.StateArgumentLabel, BrowserCommandStrings.ElementStateArgumentDescription, "visible", required: false, ["attached", "detached", "visible", "hidden", "enabled", "disabled", "checked", "unchecked"]), + CreateNumberArgument("timeoutMilliseconds", BrowserCommandStrings.TimeoutMillisecondsArgumentLabel, BrowserCommandStrings.TimeoutMillisecondsArgumentDescription, DefaultBrowserCommandTimeoutMilliseconds.ToString(CultureInfo.InvariantCulture), required: false) + ])) + .WithCommand( + CaptureScreenshotCommandName, + BrowserCommandStrings.CaptureScreenshotName, + async context => + { + try + { + var format = GetOptionalStringArgument(context.Arguments, "format") ?? "png"; + if (format is not ("png" or "jpeg" or "webp")) + { + throw new InvalidOperationException("Screenshot format must be 'png', 'jpeg', or 'webp'."); + } + + var quality = GetOptionalNullableIntegerArgument(context.Arguments, "quality", 0, 100); + var fullPage = GetOptionalBooleanArgument(context.Arguments, "fullPage", defaultValue: false); + var sessionManager = context.ServiceProvider.GetRequiredService(); + var result = await sessionManager.CaptureScreenshotAsync(context.ResourceName, new BrowserScreenshotCaptureOptions(format, quality, fullPage), context.CancellationToken).ConfigureAwait(false); + var resultJson = JsonSerializer.Serialize( + new BrowserLogsScreenshotCommandResult( + result.Artifact.ResourceName, + result.SessionId, + result.Browser, + result.BrowserExecutable, + result.BrowserHostOwnership, + result.ProcessId, + result.TargetId, + result.TargetUrl.ToString(), + result.Artifact.FilePath, + result.Artifact.MimeType, + result.Artifact.SizeBytes, + result.Artifact.CreatedAt), + s_commandResultJsonOptions); + + return CommandResults.Success( + $"Captured screenshot to '{result.Artifact.FilePath}'.", + new CommandResultData + { + Value = resultJson, + Format = CommandResultFormat.Json, + DisplayImmediately = true + }); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + new CommandOptions + { + Description = BrowserCommandStrings.CaptureScreenshotDescription, + IconName = "Camera", + IconVariant = IconVariant.Regular, + ArgumentInputs = + [ + CreateChoiceArgument("format", BrowserCommandStrings.ScreenshotFormatArgumentLabel, BrowserCommandStrings.ScreenshotFormatArgumentDescription, "png", required: false, ["png", "jpeg", "webp"]), + CreateNumberArgument("quality", BrowserCommandStrings.ScreenshotQualityArgumentLabel, BrowserCommandStrings.ScreenshotQualityArgumentDescription, value: null, required: false), + CreateBooleanArgument("fullPage", BrowserCommandStrings.FullPageArgumentLabel, BrowserCommandStrings.FullPageArgumentDescription, value: "false", required: false) + ], + UpdateState = context => + { + var childState = context.ResourceSnapshot.State?.Text; + return childState == KnownResourceStates.Running + ? ResourceCommandState.Enabled + : ResourceCommandState.Disabled; + } + }) + .WithCommand( + CloseTrackedBrowserCommandName, + BrowserCommandStrings.CloseTrackedBrowserName, + async context => + { + try + { + var sessionManager = context.ServiceProvider.GetRequiredService(); + var resultJson = await sessionManager.CloseActiveSessionAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); + return BrowserJsonCommandSuccess(BrowserCommandStrings.CloseTrackedBrowserSucceeded, resultJson); + } + catch (Exception ex) + { + return CommandResults.Failure(ex.Message); + } + }, + new CommandOptions + { + Description = BrowserCommandStrings.CloseTrackedBrowserDescription, + IconName = "Dismiss", + IconVariant = IconVariant.Regular, + UpdateState = BrowserSessionCommandState + }); + + builder.OnBeforeResourceStarted((_, @event, _) => RefreshBrowserAutomationResourceAsync(@event.Services.GetRequiredService())) + .OnResourceReady((_, @event, _) => RefreshBrowserAutomationResourceAsync(@event.Services.GetRequiredService())) + .OnResourceStopped((_, @event, _) => RefreshBrowserAutomationResourceAsync(@event.Services.GetRequiredService())); + + return builder; + + Task RefreshBrowserAutomationResourceAsync(ResourceNotificationService notifications) => + notifications.PublishUpdateAsync(browserAutomationResource, snapshot => snapshot); + + static Task ExecuteUnifiedWaitAsync( + IBrowserLogsSessionManager sessionManager, + string resourceName, + string? selector, + string? text, + string? urlContains, + string? url, + string match, + string? loadState, + string? elementState, + string? function, + int timeoutMilliseconds, + CancellationToken cancellationToken) + { + if (!string.IsNullOrWhiteSpace(function)) + { + return sessionManager.WaitForFunctionAsync(resourceName, function, timeoutMilliseconds, cancellationToken); + } + + if (!string.IsNullOrWhiteSpace(loadState)) + { + return sessionManager.WaitForLoadStateAsync(resourceName, loadState, timeoutMilliseconds, cancellationToken); + } + + if (!string.IsNullOrWhiteSpace(urlContains)) + { + return sessionManager.WaitForUrlAsync(resourceName, urlContains, "contains", timeoutMilliseconds, cancellationToken); + } + + if (!string.IsNullOrWhiteSpace(url)) + { + return sessionManager.WaitForUrlAsync(resourceName, url, match, timeoutMilliseconds, cancellationToken); + } + + if (!string.IsNullOrWhiteSpace(elementState)) + { + if (string.IsNullOrWhiteSpace(selector)) + { + throw new InvalidOperationException("Browser command argument 'selector' is required when 'elementState' is specified."); + } + + return sessionManager.WaitForElementStateAsync(resourceName, selector, elementState, timeoutMilliseconds, cancellationToken); + } + + return sessionManager.WaitForAsync(resourceName, selector, text, timeoutMilliseconds, cancellationToken); + } + + static ImmutableArray CreateInitialProperties(string resourceName, BrowserConfiguration configuration) + { + List properties = + [ + new(CustomResourceKnownProperties.Source, resourceName), + new(BrowserPropertyName, configuration.Browser), + new(UserDataModePropertyName, configuration.UserDataMode.ToString()) + ]; + + if (configuration.Profile is { } profile) + { + properties.Add(new ResourcePropertySnapshot(ProfilePropertyName, profile)); + } + + properties.AddRange( + [ + new ResourcePropertySnapshot(ActiveSessionCountPropertyName, 0), + new ResourcePropertySnapshot(ActiveSessionsPropertyName, "None"), + new ResourcePropertySnapshot(BrowserSessionsPropertyName, "[]"), + new ResourcePropertySnapshot(TotalSessionsLaunchedPropertyName, 0) + ]); + + return [.. properties]; + } + + static Uri ResolveBrowserUrl(T resource) + { + EndpointAnnotation? endpointAnnotation = null; + if (resource.TryGetAnnotationsOfType(out var endpoints)) + { + endpointAnnotation = endpoints.FirstOrDefault(e => e.UriScheme == "https") + ?? endpoints.FirstOrDefault(e => e.UriScheme == "http"); + } + + if (endpointAnnotation is null) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserAutomationResourceMissingHttpEndpoint, resource.Name)); + } + + var endpointReference = resource.GetEndpoint(endpointAnnotation.Name); + if (!endpointReference.IsAllocated) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsEndpointNotAllocated, endpointAnnotation.Name, resource.Name)); + } + + return new Uri(endpointReference.Url, UriKind.Absolute); + } + + static void ThrowIfBlankWhenSpecified(string? value, string paramName) + { + if (value is not null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value, paramName); + } + } + } + + /// + /// Adds a child resource that can open the application's primary browser endpoint in a tracked browser session, + /// surface browser diagnostics, automate browser interactions, and capture screenshots. + /// + /// The type of resource being configured. + /// The resource builder. + /// The browser to launch. + /// Optional Chromium profile name or directory name to use. + /// Optional that selects the tracked browser user data directory mode. + /// A reference to the original for further chaining. + [Obsolete("Use WithBrowserAutomation instead.")] + [Experimental("ASPIREBROWSERAUTOMATION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] + public static IResourceBuilder WithBrowserLogs( + this IResourceBuilder builder, + string? browser = null, + string? profile = null, + BrowserUserDataMode? userDataMode = null) + where T : IResourceWithEndpoints + { + return builder.WithBrowserAutomation(browser, profile, userDataMode); + } + + private static CommandOptions CreateBrowserCommandOptions(string description, string iconName, IReadOnlyList argumentInputs) + { + return new CommandOptions + { + Description = description, + IconName = iconName, + IconVariant = IconVariant.Regular, + ArgumentInputs = argumentInputs, + Visibility = ResourceCommandVisibility.Api, + UpdateState = BrowserSessionCommandState + }; + } + + private static ResourceCommandState BrowserSessionCommandState(UpdateCommandStateContext context) + { + return context.ResourceSnapshot.State?.Text == KnownResourceStates.Running + ? ResourceCommandState.Enabled + : ResourceCommandState.Disabled; + } + + private static ExecuteCommandResult BrowserJsonCommandSuccess(string message, string resultJson) + { + return CommandResults.Success( + message, + new CommandResultData + { + Value = resultJson, + Format = CommandResultFormat.Json, + DisplayImmediately = true + }); + } + + private static async Task AddSnapshotAfterAsync(IBrowserLogsSessionManager sessionManager, string resourceName, string resultJson, bool snapshotAfter, CancellationToken cancellationToken) + { + if (!snapshotAfter) + { + return resultJson; + } + + var snapshotJson = await sessionManager.GetPageSnapshotAsync(resourceName, DefaultSnapshotMaxElements, DefaultSnapshotMaxTextLength, cancellationToken).ConfigureAwait(false); + + // Browser action and snapshot command results are JSON objects shaped like: + // { "action": "click", ... } and { "action": "snapshot", "elements": [ ... ] }. + var result = JsonNode.Parse(resultJson)?.AsObject() + ?? throw new InvalidOperationException("Browser command returned an invalid JSON object."); + var snapshot = JsonNode.Parse(snapshotJson)?.AsObject() + ?? throw new InvalidOperationException("Browser snapshot command returned an invalid JSON object."); + + result["snapshotAfter"] = true; + result["snapshot"] = snapshot; + + return result.ToJsonString(s_commandResultJsonOptions); + } + + private static InteractionInput CreateSelectorArgument(bool required = true) + { + return CreateTextArgument( + "selector", + BrowserCommandStrings.SelectorArgumentLabel, + BrowserCommandStrings.SelectorArgumentDescription, + required, + placeholder: "#submit"); + } + + private static InteractionInput CreateSnapshotAfterArgument() + { + return CreateBooleanArgument( + "snapshotAfter", + BrowserCommandStrings.SnapshotAfterArgumentLabel, + BrowserCommandStrings.SnapshotAfterArgumentDescription, + value: "false", + required: false); + } + + private static InteractionInput CreateTextArgument(string name, string label, string description, bool required, string? placeholder = null) + { + return new InteractionInput + { + Name = name, + Label = label, + Description = description, + InputType = InputType.Text, + Required = required, + Placeholder = placeholder, + MaxLength = 50_000 + }; + } + + private static InteractionInput CreateBooleanArgument(string name, string label, string description, string value, bool required) + { + return new InteractionInput + { + Name = name, + Label = label, + Description = description, + InputType = InputType.Boolean, + Required = required, + Value = value + }; + } + + private static InteractionInput CreateNumberArgument(string name, string label, string description, string? value, bool required) + { + return new InteractionInput + { + Name = name, + Label = label, + Description = description, + InputType = InputType.Number, + Required = required, + Value = value + }; + } + + private static InteractionInput CreateChoiceArgument(string name, string label, string description, string? value, bool required, IReadOnlyList options) + { + return new InteractionInput + { + Name = name, + Label = label, + Description = description, + InputType = InputType.Choice, + Required = required, + Value = value, + Options = options.Select(option => new KeyValuePair(option, option)).ToArray() + }; + } + + private static string GetRequiredStringArgument(JsonElement? arguments, string name, bool allowEmpty = false) + { + if (!TryGetStringArgument(arguments, name, out var value) || (!allowEmpty && string.IsNullOrWhiteSpace(value))) + { + throw new InvalidOperationException($"Browser command argument '{name}' is required."); + } + + return value ?? string.Empty; + } + + private static string? GetOptionalStringArgument(JsonElement? arguments, string name) + { + if (!TryGetStringArgument(arguments, name, out var value)) + { + return null; + } + + return string.IsNullOrWhiteSpace(value) ? null : value; + } + + private static bool TryGetStringArgument(JsonElement? arguments, string name, out string? result) + { + if (!TryGetArgumentObject(arguments, out var argumentObject) || !argumentObject.TryGetProperty(name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined) + { + result = null; + return false; + } + + if (value.ValueKind != JsonValueKind.String) + { + throw new InvalidOperationException($"Browser command argument '{name}' must be a string."); + } + + result = value.GetString(); + return true; + } + + private static int GetOptionalIntegerArgument(JsonElement? arguments, string name, int defaultValue, int minimum, int maximum) + { + if (!TryGetArgumentObject(arguments, out var argumentObject) || !argumentObject.TryGetProperty(name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined) + { + return defaultValue; + } + + int result; + if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var number)) + { + result = number; + } + else if (value.ValueKind == JsonValueKind.String && int.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var stringNumber)) + { + result = stringNumber; + } + else + { + throw new InvalidOperationException($"Browser command argument '{name}' must be an integer."); + } + + if (result < minimum || result > maximum) + { + throw new InvalidOperationException($"Browser command argument '{name}' must be between {minimum} and {maximum}."); + } + + return result; + } + + private static int? GetOptionalNullableIntegerArgument(JsonElement? arguments, string name, int minimum, int maximum) + { + if (!TryGetArgumentObject(arguments, out var argumentObject) || !argumentObject.TryGetProperty(name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined) + { + return null; + } + + int result; + if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var number)) + { + result = number; + } + else if (value.ValueKind == JsonValueKind.String && int.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var stringNumber)) + { + result = stringNumber; + } + else + { + throw new InvalidOperationException($"Browser command argument '{name}' must be an integer."); + } + + if (result < minimum || result > maximum) + { + throw new InvalidOperationException($"Browser command argument '{name}' must be between {minimum} and {maximum}."); + } + + return result; + } + + private static bool GetOptionalBooleanArgument(JsonElement? arguments, string name, bool defaultValue) + { + if (!TryGetArgumentObject(arguments, out var argumentObject) || !argumentObject.TryGetProperty(name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined) + { + return defaultValue; + } + + return value.ValueKind switch + { + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.String when bool.TryParse(value.GetString(), out var result) => result, + _ => throw new InvalidOperationException($"Browser command argument '{name}' must be a boolean.") + }; + } + + private static bool TryGetArgumentObject(JsonElement? arguments, out JsonElement argumentObject) + { + // Browser command arguments are JSON objects shaped like: + // { "selector": "...", "value": "...", "timeoutMilliseconds": 10000 } + if (arguments is not { } value || value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null) + { + argumentObject = default; + return false; + } + + if (value.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException("Browser command arguments must be a JSON object."); + } + + argumentObject = value; + return true; + } + + private sealed record BrowserLogsScreenshotCommandResult( + string ResourceName, + string SessionId, + string Browser, + string BrowserExecutable, + string BrowserHostOwnership, + int? ProcessId, + string TargetId, + string TargetUrl, + string Path, + string MimeType, + long SizeBytes, + DateTimeOffset CreatedAt); +} diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsResource.cs b/src/Aspire.Hosting.Browsers/BrowserAutomationResource.cs similarity index 96% rename from src/Aspire.Hosting.Browsers/BrowserLogsResource.cs rename to src/Aspire.Hosting.Browsers/BrowserAutomationResource.cs index 7c407280e98..e0f926a06a8 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsResource.cs +++ b/src/Aspire.Hosting.Browsers/BrowserAutomationResource.cs @@ -6,7 +6,7 @@ namespace Aspire.Hosting; -internal sealed class BrowserLogsResource( +internal sealed class BrowserAutomationResource( string name, IResourceWithEndpoints parentResource, BrowserConfiguration initialConfiguration, diff --git a/src/Aspire.Hosting.Browsers/BrowserConfiguration.cs b/src/Aspire.Hosting.Browsers/BrowserConfiguration.cs index 47e03c15f69..478256dde3f 100644 --- a/src/Aspire.Hosting.Browsers/BrowserConfiguration.cs +++ b/src/Aspire.Hosting.Browsers/BrowserConfiguration.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only using System.Globalization; using Aspire.Hosting.Browsers.Resources; @@ -42,15 +42,39 @@ internal static BrowserConfiguration Resolve( ArgumentNullException.ThrowIfNull(configuration); ArgumentException.ThrowIfNullOrWhiteSpace(resourceName); - var browserLogsSection = configuration.GetSection(BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName); - var resourceSection = browserLogsSection.GetSection(resourceName); + var browserAutomationSection = configuration.GetSection(BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName); + var resourceSection = browserAutomationSection.GetSection(resourceName); + var legacyBrowserLogsSection = configuration.GetSection(BrowserAutomationBuilderExtensions.LegacyBrowserLogsConfigurationSectionName); + var legacyResourceSection = legacyBrowserLogsSection.GetSection(resourceName); - // Resolution order is explicit argument -> resource-specific config -> global browser-log config -> default. + // Resolution order is explicit argument -> resource-specific config -> global browser automation config -> default. // Resolve user-data mode before browser so the browser default can prefer Edge for shared state and Chrome for // disposable isolated state. - var resolvedProfile = ResolveProfile(explicitValues, resourceRuntimeConfiguration, globalRuntimeConfiguration, resourceSection, browserLogsSection); - var resolvedUserDataMode = ResolveUserDataMode(explicitValues, resourceRuntimeConfiguration, globalRuntimeConfiguration, resourceSection, browserLogsSection); - var resolvedBrowser = ResolveBrowser(explicitValues, resourceRuntimeConfiguration, globalRuntimeConfiguration, resourceSection, browserLogsSection, resolvedUserDataMode); + var resolvedProfile = ResolveProfile( + explicitValues, + resourceRuntimeConfiguration, + globalRuntimeConfiguration, + resourceSection, + browserAutomationSection, + legacyResourceSection, + legacyBrowserLogsSection); + var resolvedUserDataMode = ResolveUserDataMode( + explicitValues, + resourceRuntimeConfiguration, + globalRuntimeConfiguration, + resourceSection, + browserAutomationSection, + legacyResourceSection, + legacyBrowserLogsSection); + var resolvedBrowser = ResolveBrowser( + explicitValues, + resourceRuntimeConfiguration, + globalRuntimeConfiguration, + resourceSection, + browserAutomationSection, + legacyResourceSection, + legacyBrowserLogsSection, + resolvedUserDataMode); if (string.IsNullOrWhiteSpace(resolvedBrowser)) { @@ -68,9 +92,9 @@ internal static BrowserConfiguration Resolve( string.Format( CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsProfileRequiresSharedUserDataMode, - BrowserLogsBuilderExtensions.ProfileConfigurationKey, + BrowserAutomationBuilderExtensions.ProfileConfigurationKey, resolvedProfile, - BrowserLogsBuilderExtensions.UserDataModeConfigurationKey, + BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey, BrowserUserDataMode.Isolated, BrowserUserDataMode.Shared)); } @@ -130,7 +154,7 @@ private static ConfigurationValue ParseUserDataMode(string? CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsInvalidUserDataModeConfiguration, value, - BrowserLogsBuilderExtensions.UserDataModeConfigurationKey, + BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey, BrowserUserDataMode.Shared, BrowserUserDataMode.Isolated)); } @@ -143,15 +167,19 @@ private static string GetDefaultBrowser(BrowserUserDataMode userDataMode) => BrowserConfiguration? resourceRuntimeConfiguration, BrowserConfiguration? globalRuntimeConfiguration, IConfigurationSection resourceSection, - IConfigurationSection browserLogsSection) + IConfigurationSection browserAutomationSection, + IConfigurationSection legacyResourceSection, + IConfigurationSection legacyBrowserLogsSection) => ResolveValue( FromOptionalString(explicitValues.Profile), resourceRuntimeConfiguration, globalRuntimeConfiguration, static configuration => configuration.Profile, resourceSection, - browserLogsSection, - BrowserLogsBuilderExtensions.ProfileConfigurationKey, + browserAutomationSection, + legacyResourceSection, + legacyBrowserLogsSection, + BrowserAutomationBuilderExtensions.ProfileConfigurationKey, FromOptionalString, static () => null); @@ -160,7 +188,9 @@ private static BrowserUserDataMode ResolveUserDataMode( BrowserConfiguration? resourceRuntimeConfiguration, BrowserConfiguration? globalRuntimeConfiguration, IConfigurationSection resourceSection, - IConfigurationSection browserLogsSection) + IConfigurationSection browserAutomationSection, + IConfigurationSection legacyResourceSection, + IConfigurationSection legacyBrowserLogsSection) => ResolveValue( explicitValues.UserDataMode is { } explicitUserDataMode ? ConfigurationValue.Present(explicitUserDataMode) @@ -169,8 +199,10 @@ explicitValues.UserDataMode is { } explicitUserDataMode globalRuntimeConfiguration, static configuration => configuration.UserDataMode, resourceSection, - browserLogsSection, - BrowserLogsBuilderExtensions.UserDataModeConfigurationKey, + browserAutomationSection, + legacyResourceSection, + legacyBrowserLogsSection, + BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey, ParseUserDataMode, static () => DefaultUserDataMode); @@ -179,7 +211,9 @@ private static string ResolveBrowser( BrowserConfiguration? resourceRuntimeConfiguration, BrowserConfiguration? globalRuntimeConfiguration, IConfigurationSection resourceSection, - IConfigurationSection browserLogsSection, + IConfigurationSection browserAutomationSection, + IConfigurationSection legacyResourceSection, + IConfigurationSection legacyBrowserLogsSection, BrowserUserDataMode resolvedUserDataMode) => ResolveValue( FromOptionalString(explicitValues.Browser), @@ -187,8 +221,10 @@ private static string ResolveBrowser( globalRuntimeConfiguration, static configuration => configuration.Browser, resourceSection, - browserLogsSection, - BrowserLogsBuilderExtensions.BrowserConfigurationKey, + browserAutomationSection, + legacyResourceSection, + legacyBrowserLogsSection, + BrowserAutomationBuilderExtensions.BrowserConfigurationKey, FromOptionalString, () => GetDefaultBrowser(resolvedUserDataMode))!; @@ -198,7 +234,9 @@ private static T ResolveValue( BrowserConfiguration? globalRuntimeConfiguration, Func getRuntimeValue, IConfigurationSection resourceSection, - IConfigurationSection browserLogsSection, + IConfigurationSection browserAutomationSection, + IConfigurationSection legacyResourceSection, + IConfigurationSection legacyBrowserLogsSection, string configurationKey, Func> parseConfigurationValue, Func getDefault) @@ -214,6 +252,11 @@ private static T ResolveValue( } var resourceConfiguration = parseConfigurationValue(resourceSection[configurationKey]); + if (!resourceConfiguration.HasValue) + { + resourceConfiguration = parseConfigurationValue(legacyResourceSection[configurationKey]); + } + if (resourceConfiguration.HasValue) { return resourceConfiguration.Value; @@ -224,7 +267,12 @@ private static T ResolveValue( return getRuntimeValue(globalRuntime); } - var globalConfiguration = parseConfigurationValue(browserLogsSection[configurationKey]); + var globalConfiguration = parseConfigurationValue(browserAutomationSection[configurationKey]); + if (!globalConfiguration.HasValue) + { + globalConfiguration = parseConfigurationValue(legacyBrowserLogsSection[configurationKey]); + } + return globalConfiguration.HasValue ? globalConfiguration.Value : getDefault(); diff --git a/src/Aspire.Hosting.Browsers/BrowserEndpointDiscovery.cs b/src/Aspire.Hosting.Browsers/BrowserEndpointDiscovery.cs index 9598f358b98..65030e0f465 100644 --- a/src/Aspire.Hosting.Browsers/BrowserEndpointDiscovery.cs +++ b/src/Aspire.Hosting.Browsers/BrowserEndpointDiscovery.cs @@ -16,7 +16,7 @@ namespace Aspire.Hosting; // // Why this exists: // - Chromium's singleton is keyed by the user data root, not by Aspire. If a WebSocket-based option already launched a -// debug-enabled browser for that root, a later browser-log session can attach to it instead of starting another +// debug-enabled browser for that root, a later browser automation session can attach to it instead of starting another // process. // - Chromium's DevToolsActivePort file is only a launch-time hand-off file and isn't enough for cross-session adoption. // This sidecar records the exact browser identity and endpoint proved during startup. diff --git a/src/Aspire.Hosting.Browsers/BrowserHostRegistry.cs b/src/Aspire.Hosting.Browsers/BrowserHostRegistry.cs index de80a3a982d..9b69deb45d7 100644 --- a/src/Aspire.Hosting.Browsers/BrowserHostRegistry.cs +++ b/src/Aspire.Hosting.Browsers/BrowserHostRegistry.cs @@ -81,7 +81,7 @@ public async Task AcquireAsync(BrowserConfiguration configurat // In Playwright terms, the user data directory is the persistent-context boundary: multiple pages can // share one browser process/context, while requests for a different named profile are rejected. // In the playground this shows up as one browser window/process with additional tracked page targets - // as more resources start browser-log sessions, rather than one browser process per session. + // as more resources start browser automation sessions, rather than one browser process per session. ValidateProfileCompatibility(identity, entry.ProfileDirectoryName, userDataDirectory.ProfileDirectoryName); entry.ReferenceCount++; _logger.LogInformation("Reusing tracked browser host '{BrowserExecutable}' at '{Endpoint}'. Active leases: {ReferenceCount}.", identity.ExecutablePath, FormatDebugEndpoint(entry.Host.DebugEndpoint), entry.ReferenceCount); diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsArtifacts.cs b/src/Aspire.Hosting.Browsers/BrowserLogsArtifacts.cs index f9f99fbdf76..1043b682555 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsArtifacts.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsArtifacts.cs @@ -5,7 +5,7 @@ namespace Aspire.Hosting; -// Writes durable browser-log command artifacts outside of command output so commands can return concise, agent-friendly +// Writes durable browser automation command artifacts outside of command output so commands can return concise, agent-friendly // summaries while large payloads stay on disk. internal interface IBrowserLogsArtifactWriter { @@ -90,7 +90,7 @@ public async Task WriteArtifactAsync( return new BrowserLogsArtifact(resourceName, artifactType, filePath, mimeType, content.Length, createdAt); } - throw new IOException($"Unable to create a unique browser-log command artifact file under '{directory}'."); + throw new IOException($"Unable to create a unique browser automation command artifact file under '{directory}'."); } private static string GetAppHostSegment(string? appHostKey) diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsBrowserAutomationScripts.cs b/src/Aspire.Hosting.Browsers/BrowserLogsBrowserAutomationScripts.cs new file mode 100644 index 00000000000..fae183aca2c --- /dev/null +++ b/src/Aspire.Hosting.Browsers/BrowserLogsBrowserAutomationScripts.cs @@ -0,0 +1,1404 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; + +namespace Aspire.Hosting; + +internal static class BrowserLogsBrowserAutomationScripts +{ + private const string Helpers = """ +const normalizeText = value => String(value ?? '').replace(/\s+/g, ' ').trim(); +const cssIdentifier = value => globalThis.CSS && CSS.escape ? CSS.escape(String(value)) : String(value).replace(/["\\#.:>+~[\]\s]/g, '\\$&'); +const cssString = value => String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"'); +const byAttribute = (name, value) => `[${name}="${cssString(value)}"]`; +const isVisible = element => { + const style = globalThis.getComputedStyle(element); + const rect = element.getBoundingClientRect(); + return style.visibility !== 'hidden' && style.display !== 'none' && rect.width > 0 && rect.height > 0; +}; +const preferredSelector = element => { + if (element.id) { + return `#${cssIdentifier(element.id)}`; + } + + for (const name of ['data-testid', 'data-test', 'data-qa', 'aria-label', 'name']) { + const value = element.getAttribute(name); + if (value) { + return `${element.localName}${byAttribute(name, value)}`; + } + } + + const path = []; + let current = element; + while (current && current.nodeType === Node.ELEMENT_NODE && path.length < 6) { + let selector = current.localName; + if (current.id) { + selector += `#${cssIdentifier(current.id)}`; + path.unshift(selector); + break; + } + + let index = 1; + let sibling = current; + while ((sibling = sibling.previousElementSibling)) { + if (sibling.localName === current.localName) { + index++; + } + } + + selector += `:nth-of-type(${index})`; + path.unshift(selector); + current = current.parentElement; + } + + return path.join(' > '); +}; +const associatedLabel = element => { + if (element.id) { + const explicitLabel = document.querySelector(`label[for="${cssString(element.id)}"]`); + if (explicitLabel) { + return normalizeText(explicitLabel.innerText || explicitLabel.textContent); + } + } + + const label = element.closest('label'); + return label ? normalizeText(label.innerText || label.textContent) : undefined; +}; +const implicitRole = element => { + if (element.getAttribute('role')) { + return element.getAttribute('role'); + } + + switch (element.localName) { + case 'a': + return element.hasAttribute('href') ? 'link' : undefined; + case 'button': + return 'button'; + case 'input': { + const type = (element.getAttribute('type') || 'text').toLowerCase(); + if (type === 'checkbox') { + return 'checkbox'; + } + if (type === 'radio') { + return 'radio'; + } + if (type === 'button' || type === 'submit' || type === 'reset') { + return 'button'; + } + return 'textbox'; + } + case 'select': + return 'combobox'; + case 'textarea': + return 'textbox'; + default: + return undefined; + } +}; +const accessibleName = element => normalizeText( + element.getAttribute('aria-label') + || associatedLabel(element) + || element.getAttribute('title') + || element.getAttribute('placeholder') + || element.innerText + || element.textContent +); +const interactiveSelector = [ + 'a[href]', + 'button', + 'input', + 'textarea', + 'select', + 'summary', + '[role]', + '[contenteditable="true"]', + '[onclick]', + '[tabindex]:not([tabindex="-1"])' +].join(','); +const getInteractiveElements = () => Array.from(document.querySelectorAll(interactiveSelector)).filter(isVisible); +const describeElement = (element, index) => { + const rect = element.getBoundingClientRect(); + const tag = element.localName; + const text = normalizeText(element.innerText || element.textContent); + const value = 'value' in element ? element.value : undefined; + const options = tag === 'select' + ? Array.from(element.options).map(option => ({ value: option.value, label: normalizeText(option.textContent), selected: option.selected })) + : undefined; + return { + index, + ref: index >= 0 ? `e${index + 1}` : undefined, + selector: preferredSelector(element), + tag, + role: implicitRole(element), + type: element.getAttribute('type') || undefined, + name: element.getAttribute('name') || undefined, + label: associatedLabel(element), + text: text ? text.slice(0, 160) : undefined, + value: value ? String(value).slice(0, 160) : undefined, + placeholder: element.getAttribute('placeholder') || undefined, + href: element.href || undefined, + checked: 'checked' in element ? Boolean(element.checked) : undefined, + disabled: 'disabled' in element ? Boolean(element.disabled) : undefined, + visible: isVisible(element), + bounds: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: Math.round(rect.width), + height: Math.round(rect.height) + }, + options + }; +}; +const describeResolvedElement = element => { + const index = getInteractiveElements().indexOf(element); + return describeElement(element, index); +}; +const findElement = selector => { + const refMatch = /^e([1-9]\d*)$/.exec(selector); + if (refMatch) { + const elements = getInteractiveElements(); + const index = Number(refMatch[1]) - 1; + const element = elements[index]; + if (!element) { + throw new Error(`No element matched snapshot ref '${selector}'. Refresh the browser snapshot and try again.`); + } + + return element; + } + + const element = document.querySelector(selector); + if (!element) { + throw new Error(`No element matched selector '${selector}'.`); + } + + return element; +}; +const findMatchingElement = (kind, value, name, index) => { + const normalizedValue = normalizeText(value); + const normalizedName = normalizeText(name); + switch (kind) { + case 'role': + return Array.from(document.querySelectorAll('*')).find(element => { + if (implicitRole(element) !== normalizedValue) { + return false; + } + + return !normalizedName || accessibleName(element).includes(normalizedName); + }); + case 'text': + return Array.from(document.querySelectorAll('body *')).find(element => normalizeText(element.innerText || element.textContent).includes(normalizedValue)); + case 'label': + return Array.from(document.querySelectorAll('input, textarea, select')).find(element => normalizeText(associatedLabel(element)).includes(normalizedValue)); + case 'placeholder': + return Array.from(document.querySelectorAll('[placeholder]')).find(element => normalizeText(element.getAttribute('placeholder')).includes(normalizedValue)); + case 'alt': + return Array.from(document.querySelectorAll('[alt]')).find(element => normalizeText(element.getAttribute('alt')).includes(normalizedValue)); + case 'title': + return Array.from(document.querySelectorAll('[title]')).find(element => normalizeText(element.getAttribute('title')).includes(normalizedValue)); + case 'testid': + return document.querySelector(`[data-testid="${cssString(value)}"], [data-test="${cssString(value)}"], [data-qa="${cssString(value)}"]`); + case 'first': + return document.querySelector(value); + case 'last': { + const elements = Array.from(document.querySelectorAll(value)); + return elements.at(-1); + } + case 'nth': { + const elements = Array.from(document.querySelectorAll(value)); + return elements[index - 1]; + } + default: + throw new Error(`Unsupported find kind '${kind}'.`); + } +}; +const serializeValue = value => { + if (value instanceof Element) { + return describeResolvedElement(value); + } + + try { + return JSON.parse(JSON.stringify(value)); + } catch { + return String(value); + } +}; +const decodeCookieComponent = value => { + try { + return decodeURIComponent(value); + } catch { + return value; + } +}; +const encodeCookieComponent = value => encodeURIComponent(String(value ?? '')); +const getPageCookies = () => document.cookie + ? document.cookie.split(/;\s*/).filter(Boolean).map(cookieText => { + const separator = cookieText.indexOf('='); + const name = separator >= 0 ? cookieText.slice(0, separator) : cookieText; + const value = separator >= 0 ? cookieText.slice(separator + 1) : ''; + return { name: decodeCookieComponent(name), value: decodeCookieComponent(value) }; + }) + : []; +const setPageCookie = cookie => { + if (!cookie.name) { + throw new Error('Cookie name is required.'); + } + + const parts = [`${encodeCookieComponent(cookie.name)}=${encodeCookieComponent(cookie.value ?? '')}`]; + parts.push(`path=${cookie.path || '/'}`); + if (cookie.domain) { + parts.push(`domain=${cookie.domain}`); + } + if (cookie.expires) { + parts.push(`expires=${cookie.expires}`); + } + + document.cookie = parts.join('; '); +}; +const clearPageCookie = (name, domain, path) => { + setPageCookie({ name, value: '', domain, path, expires: 'Thu, 01 Jan 1970 00:00:00 GMT' }); +}; +const getStorage = area => { + switch (area) { + case 'local': + return localStorage; + case 'session': + return sessionStorage; + default: + throw new Error(`Unsupported storage area '${area}'.`); + } +}; +const getStorageEntries = area => { + const storage = getStorage(area); + return Object.fromEntries(Array.from({ length: storage.length }, (_, index) => { + const key = storage.key(index); + return [key, key === null ? null : storage.getItem(key)]; + }).filter(([key]) => key !== null)); +}; +const setStorageEntries = (area, entries, clearExisting) => { + const storage = getStorage(area); + if (clearExisting) { + storage.clear(); + } + + for (const [key, value] of Object.entries(entries ?? {})) { + storage.setItem(key, String(value ?? '')); + } +}; +const setElementValue = (element, value) => { + if (element.isContentEditable) { + element.textContent = value; + return; + } + + if (!('value' in element)) { + throw new Error(`Element '${preferredSelector(element)}' cannot receive text input.`); + } + + const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), 'value'); + if (descriptor?.set) { + descriptor.set.call(element, value); + } else { + element.value = value; + } +}; +const dispatchInputEvents = element => { + element.dispatchEvent(new Event('input', { bubbles: true })); + element.dispatchEvent(new Event('change', { bubbles: true })); +}; +const dispatchMouseEvent = (element, type, clientX, clientY) => { + const eventInit = { bubbles: true, cancelable: true, clientX, clientY, view: window }; + element.dispatchEvent(new MouseEvent(type, eventInit)); +}; +const getKeyboardTarget = selector => { + if (selector) { + return findElement(selector); + } + + const activeElement = document.activeElement; + if (!activeElement || activeElement === document.body) { + throw new Error('No focused element is available. Provide a selector or focus an element first.'); + } + + return activeElement; +}; +const dispatchKeyboardEvent = (element, type, key) => { + const eventOptions = { key, bubbles: true, cancelable: true }; + element.dispatchEvent(new KeyboardEvent(type, eventOptions)); +}; +const mouseButton = button => { + switch (button ?? 'left') { + case 'left': + return 0; + case 'middle': + return 1; + case 'right': + return 2; + default: + throw new Error(`Unsupported mouse button '${button}'.`); + } +}; +const dispatchMouseAt = (type, x, y, button, deltaX, deltaY) => { + const target = document.elementFromPoint(x, y) || document.body || document.documentElement; + const buttonNumber = mouseButton(button); + const eventInit = { bubbles: true, cancelable: true, clientX: x, clientY: y, button: buttonNumber, buttons: type === 'mousedown' ? 1 : 0, view: window }; + if (type === 'wheel') { + target.dispatchEvent(new WheelEvent('wheel', { ...eventInit, deltaX, deltaY })); + return target; + } + + target.dispatchEvent(new MouseEvent(type, eventInit)); + return target; +}; +const focusElement = element => { + element.scrollIntoView({ block: 'center', inline: 'center' }); + if (typeof element.focus === 'function') { + element.focus({ preventScroll: true }); + } +}; +const isEnabled = element => !element.disabled && element.getAttribute('aria-disabled') !== 'true'; +const isChecked = element => { + if ('checked' in element) { + return Boolean(element.checked); + } + + return element.getAttribute('aria-checked') === 'true'; +}; +const appendText = (element, text) => { + if (element.isContentEditable) { + if (document.queryCommandSupported?.('insertText')) { + document.execCommand('insertText', false, text); + } else { + element.textContent = `${element.textContent ?? ''}${text}`; + } + dispatchInputEvents(element); + return; + } + + if (!('value' in element)) { + throw new Error(`Element '${preferredSelector(element)}' cannot receive text input.`); + } + + const currentValue = String(element.value ?? ''); + const selectionStart = typeof element.selectionStart === 'number' ? element.selectionStart : currentValue.length; + const selectionEnd = typeof element.selectionEnd === 'number' ? element.selectionEnd : selectionStart; + setElementValue(element, `${currentValue.slice(0, selectionStart)}${text}${currentValue.slice(selectionEnd)}`); + if (typeof element.setSelectionRange === 'function') { + const position = selectionStart + text.length; + element.setSelectionRange(position, position); + } + dispatchInputEvents(element); +}; +"""; + + public static string CreateSnapshotExpression(int maxElements, int maxTextLength) + { + return CreateExpression($$""" +{{Helpers}} +const maxElements = {{maxElements}}; +const maxTextLength = {{maxTextLength}}; +const elements = getInteractiveElements() + .slice(0, maxElements) + .map(describeElement); +return { + action: 'snapshot', + url: location.href, + title: document.title, + readyState: document.readyState, + viewport: { width: innerWidth, height: innerHeight }, + text: normalizeText(document.body?.innerText ?? '').slice(0, maxTextLength), + elements +}; +"""); + } + + public static string CreateUrlExpression() + { + return CreateExpression($$""" +{{Helpers}} +return { + action: 'url', + url: location.href, + title: document.title, + readyState: document.readyState +}; +"""); + } + + public static string CreateFramesExpression() + { + return CreateExpression($$""" +{{Helpers}} +const frames = Array.from(document.querySelectorAll('iframe, frame')).map((element, index) => { + let sameOrigin = false; + let title = null; + let href = null; + try { + sameOrigin = Boolean(element.contentWindow?.document); + title = element.contentWindow?.document?.title ?? null; + href = element.contentWindow?.location?.href ?? null; + } catch { + sameOrigin = false; + } + + return { + index: index + 1, + selector: preferredSelector(element), + tagName: element.tagName.toLowerCase(), + name: element.getAttribute('name'), + id: element.id || null, + src: element.getAttribute('src'), + title, + url: href, + sameOrigin, + visible: isVisible(element) + }; +}); + +return { + action: 'frames', + url: location.href, + count: frames.length, + frames +}; +"""); + } + + public static string CreateHistoryNavigationExpression(string action) + { + return CreateExpression($$""" +{{Helpers}} +const action = {{JsonLiteral(action)}}; +const beforeUrl = location.href; +switch (action) { + case 'back': + history.back(); + break; + case 'forward': + history.forward(); + break; + case 'reload': + location.reload(); + break; + default: + throw new Error(`Unsupported navigation action '${action}'.`); +} + +return { + action, + beforeUrl, + url: location.href +}; +"""); + } + + public static string CreateGetExpression(string property, string? selector, string? name) + { + return CreateExpression($$""" +{{Helpers}} +const property = {{JsonLiteral(property)}}; +const selector = {{JsonLiteral(selector)}}; +const name = {{JsonLiteral(name)}}; +const element = selector && property !== 'count' ? findElement(selector) : undefined; +let value; +switch (property) { + case 'title': + value = document.title; + break; + case 'url': + value = location.href; + break; + case 'text': + value = normalizeText(element ? (element.innerText || element.textContent) : (document.body?.innerText ?? '')); + break; + case 'html': + value = element ? element.outerHTML : document.documentElement.outerHTML; + break; + case 'value': + if (!element) { + throw new Error("The 'value' property requires a selector."); + } + value = 'value' in element ? element.value : element.getAttribute('value'); + break; + case 'attr': + if (!element || !name) { + throw new Error("The 'attr' property requires selector and name arguments."); + } + value = element.getAttribute(name); + break; + case 'count': + if (!selector) { + throw new Error("The 'count' property requires a selector."); + } + if (/^e([1-9]\d*)$/.test(selector)) { + findElement(selector); + value = 1; + } else { + value = document.querySelectorAll(selector).length; + } + break; + case 'box': + if (!element) { + throw new Error("The 'box' property requires a selector."); + } + value = describeResolvedElement(element).bounds; + break; + case 'styles': + if (!element) { + throw new Error("The 'styles' property requires a selector."); + } + const styles = getComputedStyle(element); + value = name + ? styles.getPropertyValue(name) + : { + display: styles.display, + visibility: styles.visibility, + color: styles.color, + backgroundColor: styles.backgroundColor, + fontSize: styles.fontSize, + fontWeight: styles.fontWeight + }; + break; + default: + throw new Error(`Unsupported get property '${property}'.`); +} + +return { + action: 'get', + url: location.href, + property, + selector, + name, + value, + element: element ? describeResolvedElement(element) : undefined +}; +"""); + } + + public static string CreateIsExpression(string state, string selector) + { + return CreateExpression($$""" +{{Helpers}} +const state = {{JsonLiteral(state)}}; +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +let value; +switch (state) { + case 'visible': + value = isVisible(element); + break; + case 'enabled': + value = isEnabled(element); + break; + case 'checked': + value = isChecked(element); + break; + default: + throw new Error(`Unsupported element state '${state}'.`); +} + +return { + action: 'is', + url: location.href, + selector, + state, + value, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateFindExpression(string kind, string value, string? name, int index) + { + return CreateExpression($$""" +{{Helpers}} +const kind = {{JsonLiteral(kind)}}; +const value = {{JsonLiteral(value)}}; +const name = {{JsonLiteral(name)}}; +const index = {{index}}; +const element = findMatchingElement(kind, value, name, index); +if (!element) { + throw new Error(`No element matched find criteria '${kind}' '${value}'.`); +} + +return { + action: 'find', + url: location.href, + kind, + value, + name, + index, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateHighlightExpression(string selector) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +element.scrollIntoView({ block: 'center', inline: 'center' }); +if ('dataset' in element) { + element.dataset.aspireBrowserLogsHighlight = 'true'; +} +element.style.outline = '3px solid #ffbf00'; +element.style.outlineOffset = '2px'; +element.style.boxShadow = '0 0 0 4px rgba(255, 191, 0, 0.35)'; +return { + action: 'highlight', + url: location.href, + selector, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateEvaluateExpression(string expression) + { + return CreateExpression($$""" +{{Helpers}} +const expression = {{JsonLiteral(expression)}}; +let evaluator; +try { + evaluator = Function(`"use strict"; return (${expression});`); +} catch { + evaluator = Function(`"use strict"; ${expression}`); +} + +let value = evaluator(); +if (typeof value === 'function') { + value = value(); +} + +value = await Promise.resolve(value); +return { + action: 'eval', + url: location.href, + expression, + valueType: value === null ? 'null' : typeof value, + value: serializeValue(value) +}; +"""); + } + + public static string CreateCookiesExpression(string action, string? name, string? value, string? domain, string? path) + { + return CreateExpression($$""" +{{Helpers}} +const action = {{JsonLiteral(action)}}; +const name = {{JsonLiteral(name)}}; +const value = {{JsonLiteral(value)}}; +const domain = {{JsonLiteral(domain)}}; +const path = {{JsonLiteral(path)}}; +let cookies; +switch (action) { + case 'get': + cookies = getPageCookies(); + if (name) { + cookies = cookies.filter(cookie => cookie.name === name); + } + break; + case 'set': + if (!name) { + throw new Error("The 'set' cookie action requires a name."); + } + setPageCookie({ name, value: value ?? '', domain, path }); + cookies = getPageCookies().filter(cookie => cookie.name === name); + break; + case 'clear': + if (name) { + clearPageCookie(name, domain, path); + } else { + for (const cookie of getPageCookies()) { + clearPageCookie(cookie.name, domain, path); + } + } + cookies = getPageCookies(); + break; + default: + throw new Error(`Unsupported cookie action '${action}'.`); +} + +return { + action: 'cookies', + url: location.href, + cookieAction: action, + name, + cookies +}; +"""); + } + + public static string CreateStorageExpression(string area, string action, string? key, string? value) + { + return CreateExpression($$""" +{{Helpers}} +const area = {{JsonLiteral(area)}}; +const action = {{JsonLiteral(action)}}; +const key = {{JsonLiteral(key)}}; +const value = {{JsonLiteral(value)}}; +const storage = getStorage(area); +let result; +switch (action) { + case 'get': + result = key ? { [key]: storage.getItem(key) } : getStorageEntries(area); + break; + case 'set': + if (!key) { + throw new Error("The 'set' storage action requires a key."); + } + storage.setItem(key, value ?? ''); + result = { [key]: storage.getItem(key) }; + break; + case 'clear': + if (key) { + storage.removeItem(key); + } else { + storage.clear(); + } + result = key ? { [key]: storage.getItem(key) } : getStorageEntries(area); + break; + default: + throw new Error(`Unsupported storage action '${action}'.`); +} + +return { + action: 'storage', + url: location.href, + area, + storageAction: action, + key, + entries: result +}; +"""); + } + + public static string CreateStateExpression(string action, string? state, bool clearExisting) + { + return CreateExpression($$""" +{{Helpers}} +const action = {{JsonLiteral(action)}}; +const stateJson = {{JsonLiteral(state)}}; +const clearExisting = {{JsonSerializer.Serialize(clearExisting)}}; +const readState = () => ({ + cookies: getPageCookies(), + localStorage: getStorageEntries('local'), + sessionStorage: getStorageEntries('session') +}); +switch (action) { + case 'get': + return { + action: 'state', + stateAction: action, + url: location.href, + origin: location.origin, + state: readState() + }; + case 'set': { + if (!stateJson) { + throw new Error("The 'set' state action requires a state JSON argument."); + } + + // The state argument is a JSON object shaped like: + // { "cookies": [{ "name": "...", "value": "..." }], "localStorage": { "key": "value" }, "sessionStorage": { "key": "value" } }. + const parsedState = JSON.parse(stateJson); + if (clearExisting) { + for (const cookie of getPageCookies()) { + clearPageCookie(cookie.name); + } + } + + for (const cookie of parsedState.cookies ?? []) { + setPageCookie(cookie); + } + + setStorageEntries('local', parsedState.localStorage, clearExisting); + setStorageEntries('session', parsedState.sessionStorage, clearExisting); + return { + action: 'state', + stateAction: action, + url: location.href, + origin: location.origin, + clearExisting, + state: readState() + }; + } + case 'clear': + for (const cookie of getPageCookies()) { + clearPageCookie(cookie.name); + } + localStorage.clear(); + sessionStorage.clear(); + return { + action: 'state', + stateAction: action, + url: location.href, + origin: location.origin, + state: readState() + }; + default: + throw new Error(`Unsupported state action '${action}'.`); +} +"""); + } + + public static string CreateClickExpression(string selector) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +focusElement(element); +element.click(); +return { + action: 'click', + url: location.href, + selector, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateDoubleClickExpression(string selector) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +focusElement(element); +const rect = element.getBoundingClientRect(); +const clientX = rect.left + rect.width / 2; +const clientY = rect.top + rect.height / 2; +dispatchMouseEvent(element, 'mousedown', clientX, clientY); +dispatchMouseEvent(element, 'mouseup', clientX, clientY); +element.click(); +dispatchMouseEvent(element, 'mousedown', clientX, clientY); +dispatchMouseEvent(element, 'mouseup', clientX, clientY); +element.click(); +element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true, clientX, clientY, view: window })); +return { + action: 'dblclick', + url: location.href, + selector, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateFillExpression(string selector, string value) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const value = {{JsonLiteral(value)}}; +const element = findElement(selector); +focusElement(element); +setElementValue(element, value); +dispatchInputEvents(element); +return { + action: 'fill', + url: location.href, + selector, + value, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateCheckExpression(string selector, bool isChecked) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const checked = {{JsonSerializer.Serialize(isChecked)}}; +const element = findElement(selector); +if (!('checked' in element)) { + throw new Error(`Element '${selector}' is not checkable.`); +} + +focusElement(element); +element.checked = checked; +dispatchInputEvents(element); +return { + action: checked ? 'check' : 'uncheck', + url: location.href, + selector, + checked: Boolean(element.checked), + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateFocusExpression(string selector) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +focusElement(element); +return { + action: 'focus', + url: location.href, + selector, + activeElementSelector: preferredSelector(document.activeElement), + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateKeyEventExpression(string type, string? selector, string key) + { + return CreateExpression($$""" +{{Helpers}} +const type = {{JsonLiteral(type)}}; +const selector = {{JsonLiteral(selector)}}; +const key = {{JsonLiteral(key)}}; +const element = getKeyboardTarget(selector); +focusElement(element); +dispatchKeyboardEvent(element, type, key); +return { + action: type, + url: location.href, + selector: selector ?? preferredSelector(element), + key, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateTypeExpression(string selector, string text) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const text = {{JsonLiteral(text)}}; +const element = findElement(selector); +focusElement(element); +for (const character of text) { + const eventOptions = { key: character, bubbles: true, cancelable: true }; + element.dispatchEvent(new KeyboardEvent('keydown', eventOptions)); + element.dispatchEvent(new KeyboardEvent('keypress', eventOptions)); + appendText(element, character); + element.dispatchEvent(new KeyboardEvent('keyup', eventOptions)); +} +return { + action: 'type', + url: location.href, + selector, + text, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreatePressExpression(string? selector, string key) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const key = {{JsonLiteral(key)}}; +const element = selector ? findElement(selector) : document.activeElement; +if (!element || element === document.body) { + throw new Error('No focused element is available. Provide a selector or focus an element first.'); +} + +focusElement(element); +const eventOptions = { key, bubbles: true, cancelable: true }; +element.dispatchEvent(new KeyboardEvent('keydown', eventOptions)); +if (key.length === 1 && ('value' in element || element.isContentEditable)) { + const currentValue = element.isContentEditable ? element.textContent ?? '' : element.value ?? ''; + setElementValue(element, currentValue + key); + dispatchInputEvents(element); +} + +if (key === 'Enter' && element.form) { + if (typeof element.form.requestSubmit === 'function') { + element.form.requestSubmit(); + } else { + element.form.submit(); + } +} + +element.dispatchEvent(new KeyboardEvent('keyup', eventOptions)); +return { + action: 'press', + url: location.href, + selector: selector ?? preferredSelector(element), + key, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateHoverExpression(string selector) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +element.scrollIntoView({ block: 'center', inline: 'center' }); +const rect = element.getBoundingClientRect(); +const clientX = rect.left + rect.width / 2; +const clientY = rect.top + rect.height / 2; +if (globalThis.PointerEvent) { + const pointerEventInit = { bubbles: true, cancelable: true, clientX, clientY, pointerType: 'mouse', isPrimary: true }; + element.dispatchEvent(new PointerEvent('pointerover', pointerEventInit)); + element.dispatchEvent(new PointerEvent('pointerenter', pointerEventInit)); + element.dispatchEvent(new PointerEvent('pointermove', pointerEventInit)); +} +dispatchMouseEvent(element, 'mouseover', clientX, clientY); +dispatchMouseEvent(element, 'mouseenter', clientX, clientY); +dispatchMouseEvent(element, 'mousemove', clientX, clientY); +return { + action: 'hover', + url: location.href, + selector, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateSelectExpression(string selector, string value) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const value = {{JsonLiteral(value)}}; +const element = findElement(selector); +if (element.localName !== 'select') { + throw new Error(`Element '${selector}' is not a select element.`); +} + +const option = Array.from(element.options).find(option => option.value === value || normalizeText(option.textContent) === value); +if (!option) { + throw new Error(`No option with value or text '${value}' was found for '${selector}'.`); +} + +focusElement(element); +element.value = option.value; +option.selected = true; +dispatchInputEvents(element); +return { + action: 'select', + url: location.href, + selector, + value: option.value, + label: normalizeText(option.textContent), + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateScrollExpression(string? selector, int deltaX, int deltaY) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const deltaX = {{deltaX}}; +const deltaY = {{deltaY}}; +const target = selector ? findElement(selector) : window; +if (target === window) { + window.scrollBy({ left: deltaX, top: deltaY, behavior: 'auto' }); +} else { + target.scrollBy({ left: deltaX, top: deltaY, behavior: 'auto' }); +} +return { + action: 'scroll', + url: location.href, + selector, + deltaX, + deltaY, + scroll: target === window + ? { x: Math.round(window.scrollX), y: Math.round(window.scrollY) } + : { x: Math.round(target.scrollLeft), y: Math.round(target.scrollTop) }, + element: target === window ? undefined : describeResolvedElement(target) +}; +"""); + } + + public static string CreateScrollIntoViewExpression(string selector) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const element = findElement(selector); +element.scrollIntoView({ block: 'center', inline: 'center' }); +return { + action: 'scroll-into-view', + url: location.href, + selector, + scroll: { x: Math.round(window.scrollX), y: Math.round(window.scrollY) }, + element: describeResolvedElement(element) +}; +"""); + } + + public static string CreateMouseExpression(string action, int x, int y, string? button, int deltaX, int deltaY) + { + return CreateExpression($$""" +{{Helpers}} +const action = {{JsonLiteral(action)}}; +const x = {{x}}; +const y = {{y}}; +const button = {{JsonLiteral(button)}}; +const deltaX = {{deltaX}}; +const deltaY = {{deltaY}}; +let element; +switch (action) { + case 'move': + element = dispatchMouseAt('mousemove', x, y, button, deltaX, deltaY); + break; + case 'down': + element = dispatchMouseAt('mousedown', x, y, button, deltaX, deltaY); + break; + case 'up': + element = dispatchMouseAt('mouseup', x, y, button, deltaX, deltaY); + break; + case 'click': + element = dispatchMouseAt('mousedown', x, y, button, deltaX, deltaY); + dispatchMouseAt('mouseup', x, y, button, deltaX, deltaY); + element.click?.(); + break; + case 'wheel': + element = dispatchMouseAt('wheel', x, y, button, deltaX, deltaY); + break; + default: + throw new Error(`Unsupported mouse action '${action}'.`); +} + +return { + action: 'mouse', + mouseAction: action, + url: location.href, + x, + y, + button: button ?? 'left', + deltaX, + deltaY, + element: element instanceof Element ? describeResolvedElement(element) : undefined +}; +"""); + } + + public static string CreateWaitForExpression(string? selector, string? text, int timeoutMilliseconds) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const text = {{JsonLiteral(text)}}; +const timeoutMilliseconds = {{timeoutMilliseconds}}; +if (!selector && !text) { + throw new Error('Provide a selector, text, or both when waiting in the browser.'); +} + +const startedAt = Date.now(); +while (Date.now() - startedAt < timeoutMilliseconds) { + const element = selector ? document.querySelector(selector) : undefined; + const selectorMatched = !selector || (element && isVisible(element)); + const textMatched = !text || normalizeText(document.body?.innerText ?? '').includes(text); + if (selectorMatched && textMatched) { + return { + action: 'wait-for', + url: location.href, + selector, + text, + elapsedMilliseconds: Date.now() - startedAt, + element: element ? describeResolvedElement(element) : undefined + }; + } + + await new Promise(resolve => setTimeout(resolve, 100)); +} + +throw new Error(`Timed out after ${timeoutMilliseconds} ms waiting for ${selector ? `selector '${selector}'` : ''}${selector && text ? ' and ' : ''}${text ? `text '${text}'` : ''}.`); +"""); + } + + public static string CreateWaitForUrlExpression(string url, string match, int timeoutMilliseconds) + { + return CreateExpression($$""" +{{Helpers}} +const expectedUrl = {{JsonLiteral(url)}}; +const match = {{JsonLiteral(match)}}; +const timeoutMilliseconds = {{timeoutMilliseconds}}; +const matchesUrl = value => { + switch (match) { + case 'exact': + return value === expectedUrl; + case 'regex': + return new RegExp(expectedUrl).test(value); + case 'contains': + return value.includes(expectedUrl); + default: + throw new Error(`Unsupported URL match mode '${match}'.`); + } +}; +const startedAt = Date.now(); +while (Date.now() - startedAt < timeoutMilliseconds) { + if (matchesUrl(location.href)) { + return { + action: 'wait-for-url', + url: location.href, + expectedUrl, + match, + elapsedMilliseconds: Date.now() - startedAt + }; + } + + await new Promise(resolve => setTimeout(resolve, 100)); +} + +throw new Error(`Timed out after ${timeoutMilliseconds} ms waiting for URL '${expectedUrl}' (${match}). Current URL: '${location.href}'.`); +"""); + } + + public static string CreateWaitForLoadStateExpression(string state, int timeoutMilliseconds) + { + return CreateExpression($$""" +{{Helpers}} +const state = {{JsonLiteral(state)}}; +const timeoutMilliseconds = {{timeoutMilliseconds}}; +const startedAt = Date.now(); +let stableSince = undefined; +let lastResourceCount = performance.getEntriesByType('resource').length; +const stateMatched = () => { + switch (state) { + case 'domcontentloaded': + return document.readyState === 'interactive' || document.readyState === 'complete'; + case 'complete': + case 'load': + return document.readyState === 'complete'; + case 'networkidle': { + if (document.readyState !== 'complete') { + stableSince = undefined; + lastResourceCount = performance.getEntriesByType('resource').length; + return false; + } + + const resourceCount = performance.getEntriesByType('resource').length; + if (resourceCount !== lastResourceCount) { + lastResourceCount = resourceCount; + stableSince = undefined; + return false; + } + + stableSince ??= Date.now(); + return Date.now() - stableSince >= 500; + } + default: + throw new Error(`Unsupported load state '${state}'.`); + } +}; +while (Date.now() - startedAt < timeoutMilliseconds) { + if (stateMatched()) { + return { + action: 'wait-for-load-state', + url: location.href, + state, + readyState: document.readyState, + elapsedMilliseconds: Date.now() - startedAt + }; + } + + await new Promise(resolve => setTimeout(resolve, 100)); +} + +throw new Error(`Timed out after ${timeoutMilliseconds} ms waiting for load state '${state}'. Current readyState: '${document.readyState}'.`); +"""); + } + + public static string CreateWaitForElementStateExpression(string selector, string state, int timeoutMilliseconds) + { + return CreateExpression($$""" +{{Helpers}} +const selector = {{JsonLiteral(selector)}}; +const state = {{JsonLiteral(state)}}; +const timeoutMilliseconds = {{timeoutMilliseconds}}; +const elementMatchesState = element => { + switch (state) { + case 'attached': + return Boolean(element); + case 'detached': + return !element; + case 'visible': + return Boolean(element) && isVisible(element); + case 'hidden': + return !element || !isVisible(element); + case 'enabled': + return Boolean(element) && isEnabled(element); + case 'disabled': + return Boolean(element) && !isEnabled(element); + case 'checked': + return Boolean(element) && isChecked(element); + case 'unchecked': + return Boolean(element) && !isChecked(element); + default: + throw new Error(`Unsupported element state '${state}'.`); + } +}; +const startedAt = Date.now(); +while (Date.now() - startedAt < timeoutMilliseconds) { + const element = document.querySelector(selector); + if (elementMatchesState(element)) { + return { + action: 'wait-for-element-state', + url: location.href, + selector, + state, + elapsedMilliseconds: Date.now() - startedAt, + element: element ? describeResolvedElement(element) : undefined + }; + } + + await new Promise(resolve => setTimeout(resolve, 100)); +} + +throw new Error(`Timed out after ${timeoutMilliseconds} ms waiting for selector '${selector}' to become '${state}'.`); +"""); + } + + public static string CreateWaitForFunctionExpression(string function, int timeoutMilliseconds) + { + return CreateExpression($$""" +{{Helpers}} +const functionBody = {{JsonLiteral(function)}}; +const timeoutMilliseconds = {{timeoutMilliseconds}}; +const evaluatePredicate = async () => { + let result = Function(`"use strict"; return (${functionBody});`)(); + if (typeof result === 'function') { + result = result(); + } + + return Boolean(await Promise.resolve(result)); +}; +const startedAt = Date.now(); +while (Date.now() - startedAt < timeoutMilliseconds) { + if (await evaluatePredicate()) { + return { + action: 'wait-for-function', + url: location.href, + function: functionBody, + elapsedMilliseconds: Date.now() - startedAt + }; + } + + await new Promise(resolve => setTimeout(resolve, 100)); +} + +throw new Error(`Timed out after ${timeoutMilliseconds} ms waiting for function '${functionBody}'.`); +"""); + } + + private static string CreateExpression(string body) + { + return $$""" +(async () => { +{{body}} +})().then(result => JSON.stringify(result)) +"""; + } + + private static string JsonLiteral(string? value) => JsonSerializer.Serialize(value); +} diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsBuilderExtensions.cs b/src/Aspire.Hosting.Browsers/BrowserLogsBuilderExtensions.cs deleted file mode 100644 index a92187b5e3b..00000000000 --- a/src/Aspire.Hosting.Browsers/BrowserLogsBuilderExtensions.cs +++ /dev/null @@ -1,358 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only - -using System.Collections.Immutable; -using Aspire.Hosting.Browsers.Resources; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Text.Json; -using Aspire.Hosting.ApplicationModel; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace Aspire.Hosting; - -/// -/// Extension methods for adding tracked browser log resources to browser-based application resources. -/// -[Experimental("ASPIREBROWSERLOGS001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] -public static class BrowserLogsBuilderExtensions -{ - internal const string BrowserResourceType = "BrowserLogs"; - internal const string BrowserLogsConfigurationSectionName = "Aspire:Hosting:BrowserLogs"; - internal const string BrowserConfigurationKey = "Browser"; - internal const string BrowserPropertyName = "Browser"; - internal const string BrowserExecutablePropertyName = "Browser executable"; - internal const string BrowserHostOwnershipPropertyName = "Browser host ownership"; - internal const string ProfileConfigurationKey = "Profile"; - internal const string ProfilePropertyName = "Profile"; - internal const string UserDataModeConfigurationKey = "UserDataMode"; - internal const string UserDataModePropertyName = "User data mode"; - internal const BrowserUserDataMode DefaultUserDataMode = BrowserConfiguration.DefaultUserDataMode; - internal const string TargetUrlPropertyName = "Target URL"; - internal const string ActiveSessionsPropertyName = "Active sessions"; - internal const string BrowserSessionsPropertyName = "Browser sessions"; - internal const string ActiveSessionCountPropertyName = "Active session count"; - internal const string TotalSessionsLaunchedPropertyName = "Total sessions launched"; - internal const string LastErrorPropertyName = "Last error"; - internal const string LastSessionPropertyName = "Last session"; - internal const string OpenTrackedBrowserCommandName = "open-tracked-browser"; - internal const string ConfigureTrackedBrowserCommandName = "configure-tracked-browser"; - internal const string CaptureScreenshotCommandName = "capture-screenshot"; - private static readonly JsonSerializerOptions s_commandResultJsonOptions = new(JsonSerializerDefaults.Web) - { - WriteIndented = true - }; - - /// - /// Adds a child resource that can open the application's primary browser endpoint in a tracked browser session, - /// surface browser diagnostics, and capture screenshots. - /// - /// The type of resource being configured. - /// The resource builder. - /// - /// The browser to launch. When not specified, the tracked browser uses the configured value from - /// Aspire:Hosting:BrowserLogs and otherwise prefers an installed "msedge" browser in shared user data - /// mode, an installed "chrome" browser in isolated user data mode, and finally falls back to "chrome". - /// Supported values include logical - /// browser names such as "msedge" and "chrome", or an explicit browser executable path. - /// - /// - /// Optional Chromium profile name or directory name to use. Only valid when the effective user data mode - /// is . When not specified, the tracked browser uses the - /// configured value from Aspire:Hosting:BrowserLogs if present. - /// - /// - /// Optional that selects whether the tracked browser launches against - /// a persistent Aspire-managed user data directory shared across all AppHosts on the machine - /// (, the default) or a per-AppHost persistent user data directory - /// (). Both modes use Aspire-managed paths under - /// %LocalAppData%\Aspire\BrowserData on Windows (or platform equivalents); the user's normal browser - /// profile is never used. When not specified, the tracked browser uses the configured value from - /// Aspire:Hosting:BrowserLogs and otherwise defaults to . - /// - /// A reference to the original for further chaining. - /// - /// - /// This method adds a child browser logs resource beneath the parent resource represented by . - /// The child resource exposes a dashboard command that launches a Chromium-based browser in a tracked mode, attaches to - /// the browser's debugging protocol, forwards browser console, error, exception, and network output to the child - /// resource's console log stream, and can capture screenshots as command artifacts. - /// - /// - /// The tracked browser session uses the Chrome DevTools - /// Protocol (CDP) to subscribe to browser runtime, log, page, and network events. - /// - /// - /// The parent resource must expose at least one HTTP or HTTPS endpoint. HTTPS endpoints are preferred over HTTP - /// endpoints when selecting the browser target URL. - /// - /// - /// Browser, profile, and user data mode settings can also be supplied from configuration - /// using Aspire:Hosting:BrowserLogs:Browser, Aspire:Hosting:BrowserLogs:Profile, - /// and Aspire:Hosting:BrowserLogs:UserDataMode, or scoped to a specific resource with - /// Aspire:Hosting:BrowserLogs:{ResourceName}:Browser, - /// Aspire:Hosting:BrowserLogs:{ResourceName}:Profile, and - /// Aspire:Hosting:BrowserLogs:{ResourceName}:UserDataMode. Explicit method arguments override configuration. - /// - /// - /// - /// Add tracked browser logs for a web front end: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// - /// builder.AddProject<Projects.WebFrontend>("web") - /// .WithExternalHttpEndpoints() - /// .WithBrowserLogs(); - /// - /// - [Experimental("ASPIREBROWSERLOGS001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] - [AspireExport(Description = "Adds a child browser logs resource that opens tracked browser sessions, captures browser logs, and captures screenshots.")] - public static IResourceBuilder WithBrowserLogs( - this IResourceBuilder builder, - string? browser = null, - string? profile = null, - BrowserUserDataMode? userDataMode = null) - where T : IResourceWithEndpoints - { - ArgumentNullException.ThrowIfNull(builder); - ThrowIfBlankWhenSpecified(browser, nameof(browser)); - ThrowIfBlankWhenSpecified(profile, nameof(profile)); - - builder.ApplicationBuilder.Services.TryAddSingleton(); - builder.ApplicationBuilder.Services.TryAddSingleton(); - builder.ApplicationBuilder.Services.TryAddSingleton(); - - var parentResource = builder.Resource; - var explicitConfigurationValues = new BrowserConfigurationExplicitValues(browser, profile, userDataMode); - var initialConfiguration = BrowserConfiguration.Resolve(builder.ApplicationBuilder.Configuration, parentResource.Name, explicitConfigurationValues); - var browserLogsResource = new BrowserLogsResource( - $"{parentResource.Name}-browser-logs", - parentResource, - initialConfiguration, - explicitConfigurationValues); - browserLogsResource.Annotations.Add(NameValidationPolicyAnnotation.None); - - builder.ApplicationBuilder.AddResource(browserLogsResource) - .WithParentRelationship(parentResource) - .ExcludeFromManifest() - .WithIconName("GlobeDesktop") - .WithInitialState(new CustomResourceSnapshot - { - ResourceType = BrowserResourceType, - CreationTimeStamp = DateTime.UtcNow, - State = KnownResourceStates.NotStarted, - Properties = CreateInitialProperties(parentResource.Name, initialConfiguration) - }) - .WithCommand( - OpenTrackedBrowserCommandName, - BrowserCommandStrings.OpenTrackedBrowserName, - async context => - { - try - { - var configuration = context.ServiceProvider.GetRequiredService(); - var configurationStore = context.ServiceProvider.GetRequiredService(); - var currentConfiguration = browserLogsResource.ResolveCurrentConfiguration(configuration, configurationStore); - var url = ResolveBrowserUrl(parentResource); - var sessionManager = context.ServiceProvider.GetRequiredService(); - await sessionManager.StartSessionAsync(browserLogsResource, currentConfiguration, context.ResourceName, url, context.CancellationToken).ConfigureAwait(false); - return CommandResults.Success(); - } - catch (Exception ex) - { - return CommandResults.Failure(ex.Message); - } - }, - new CommandOptions - { - Description = BrowserCommandStrings.OpenTrackedBrowserDescription, - IconName = "Open", - IconVariant = IconVariant.Regular, - IsHighlighted = true, - UpdateState = context => - { - var childState = context.ResourceSnapshot.State?.Text; - if (childState == KnownResourceStates.Starting) - { - return ResourceCommandState.Disabled; - } - - var resourceNotifications = context.ServiceProvider.GetRequiredService(); - if (resourceNotifications.TryGetCurrentState(parentResource.Name, out var resourceEvent)) - { - var parentState = resourceEvent.Snapshot.State?.Text; - if (parentState == KnownResourceStates.Running || parentState == KnownResourceStates.RuntimeUnhealthy) - { - return ResourceCommandState.Enabled; - } - } - - return ResourceCommandState.Disabled; - } - }) - .WithCommand( - ConfigureTrackedBrowserCommandName, - BrowserCommandStrings.ConfigureTrackedBrowserName, - async context => - { - try - { - var configurationManager = context.ServiceProvider.GetRequiredService(); - return await configurationManager.ConfigureAsync(browserLogsResource, context.CancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - return CommandResults.Failure(ex.Message); - } - }, - new CommandOptions - { - Description = BrowserCommandStrings.ConfigureTrackedBrowserDescription, - IconName = "Settings", - IconVariant = IconVariant.Regular, - UpdateState = context => - { - var interactionService = context.ServiceProvider.GetRequiredService(); - return interactionService.IsAvailable - ? ResourceCommandState.Enabled - : ResourceCommandState.Disabled; - } - }) - .WithCommand( - CaptureScreenshotCommandName, - BrowserCommandStrings.CaptureScreenshotName, - async context => - { - try - { - var sessionManager = context.ServiceProvider.GetRequiredService(); - var result = await sessionManager.CaptureScreenshotAsync(context.ResourceName, context.CancellationToken).ConfigureAwait(false); - var resultJson = JsonSerializer.Serialize( - new BrowserLogsScreenshotCommandResult( - result.Artifact.ResourceName, - result.SessionId, - result.Browser, - result.BrowserExecutable, - result.BrowserHostOwnership, - result.ProcessId, - result.TargetId, - result.TargetUrl.ToString(), - result.Artifact.FilePath, - result.Artifact.MimeType, - result.Artifact.SizeBytes, - result.Artifact.CreatedAt), - s_commandResultJsonOptions); - - return CommandResults.Success( - $"Captured screenshot to '{result.Artifact.FilePath}'.", - new CommandResultData - { - Value = resultJson, - Format = CommandResultFormat.Json, - DisplayImmediately = true - }); - } - catch (Exception ex) - { - return CommandResults.Failure(ex.Message); - } - }, - new CommandOptions - { - Description = BrowserCommandStrings.CaptureScreenshotDescription, - IconName = "Camera", - IconVariant = IconVariant.Regular, - UpdateState = context => - { - var childState = context.ResourceSnapshot.State?.Text; - return childState == KnownResourceStates.Running - ? ResourceCommandState.Enabled - : ResourceCommandState.Disabled; - } - }); - - builder.OnBeforeResourceStarted((_, @event, _) => RefreshBrowserLogsResourceAsync(@event.Services.GetRequiredService())) - .OnResourceReady((_, @event, _) => RefreshBrowserLogsResourceAsync(@event.Services.GetRequiredService())) - .OnResourceStopped((_, @event, _) => RefreshBrowserLogsResourceAsync(@event.Services.GetRequiredService())); - - return builder; - - Task RefreshBrowserLogsResourceAsync(ResourceNotificationService notifications) => - notifications.PublishUpdateAsync(browserLogsResource, snapshot => snapshot); - - static ImmutableArray CreateInitialProperties(string resourceName, BrowserConfiguration configuration) - { - List properties = - [ - new(CustomResourceKnownProperties.Source, resourceName), - new(BrowserPropertyName, configuration.Browser), - new(UserDataModePropertyName, configuration.UserDataMode.ToString()) - ]; - - if (configuration.Profile is { } profile) - { - properties.Add(new ResourcePropertySnapshot(ProfilePropertyName, profile)); - } - - properties.AddRange( - [ - new ResourcePropertySnapshot(ActiveSessionCountPropertyName, 0), - new ResourcePropertySnapshot(ActiveSessionsPropertyName, "None"), - new ResourcePropertySnapshot(BrowserSessionsPropertyName, "[]"), - new ResourcePropertySnapshot(TotalSessionsLaunchedPropertyName, 0) - ]); - - return [.. properties]; - } - - static Uri ResolveBrowserUrl(T resource) - { - EndpointAnnotation? endpointAnnotation = null; - if (resource.TryGetAnnotationsOfType(out var endpoints)) - { - endpointAnnotation = endpoints.FirstOrDefault(e => e.UriScheme == "https") - ?? endpoints.FirstOrDefault(e => e.UriScheme == "http"); - } - - if (endpointAnnotation is null) - { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsResourceMissingHttpEndpoint, resource.Name)); - } - - var endpointReference = resource.GetEndpoint(endpointAnnotation.Name); - if (!endpointReference.IsAllocated) - { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, BrowserMessageStrings.BrowserLogsEndpointNotAllocated, endpointAnnotation.Name, resource.Name)); - } - - return new Uri(endpointReference.Url, UriKind.Absolute); - } - - static void ThrowIfBlankWhenSpecified(string? value, string paramName) - { - if (value is not null) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value, paramName); - } - } - } - - private sealed record BrowserLogsScreenshotCommandResult( - string ResourceName, - string SessionId, - string Browser, - string BrowserExecutable, - string BrowserHostOwnership, - int? ProcessId, - string TargetId, - string TargetUrl, - string Path, - string MimeType, - long SizeBytes, - DateTimeOffset CreatedAt); -} diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnection.cs b/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnection.cs index 2e0c268a5a1..654d7a7c3e3 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnection.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnection.cs @@ -25,9 +25,13 @@ internal interface IBrowserLogsCdpConnection : IAsyncDisposable Task EnablePageInstrumentationAsync(string sessionId, CancellationToken cancellationToken); - Task CaptureScreenshotAsync(string sessionId, CancellationToken cancellationToken); + Task CaptureScreenshotAsync(string sessionId, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken); Task NavigateAsync(string sessionId, Uri url, CancellationToken cancellationToken); + + Task EvaluateAsync(string sessionId, string expression, TimeSpan? timeout, CancellationToken cancellationToken); + + Task SendRawCommandAsync(string? sessionId, string method, string? parametersJson, CancellationToken cancellationToken); } // Owns one browser-level CDP transport. Protocol parsing stays in BrowserLogsCdpProtocol, while page lifecycle and @@ -173,15 +177,23 @@ public async Task EnablePageInstrumentationAsync(string sessionId, CancellationT await SendCommandAsync(BrowserLogsCdpProtocol.NetworkEnableMethod, sessionId, writeParameters: null, BrowserLogsCdpProtocol.ParseCommandAckResponse, cancellationToken).ConfigureAwait(false); } - public Task CaptureScreenshotAsync(string sessionId, CancellationToken cancellationToken) + public Task CaptureScreenshotAsync(string sessionId, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { return SendCommandAsync( BrowserLogsCdpProtocol.PageCaptureScreenshotMethod, sessionId, - static writer => + writer => { - writer.WriteString("format", "png"); + writer.WriteString("format", options.Format); writer.WriteBoolean("fromSurface", true); + if (options.Quality is { } quality) + { + writer.WriteNumber("quality", quality); + } + if (options.FullPage) + { + writer.WriteBoolean("captureBeyondViewport", true); + } }, BrowserLogsCdpProtocol.ParseCaptureScreenshotResponse, cancellationToken, @@ -198,6 +210,39 @@ public Task NavigateAsync(string sessionId, Uri url, Canc cancellationToken); } + public Task EvaluateAsync(string sessionId, string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + + return SendCommandAsync( + BrowserLogsCdpProtocol.RuntimeEvaluateMethod, + sessionId, + writer => + { + writer.WriteString("expression", expression); + writer.WriteBoolean("awaitPromise", true); + writer.WriteBoolean("returnByValue", true); + writer.WriteBoolean("userGesture", true); + }, + BrowserLogsCdpProtocol.ParseRuntimeEvaluateResponse, + cancellationToken, + timeout); + } + + public Task SendRawCommandAsync(string? sessionId, string method, string? parametersJson, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(method); + + return SendCommandAsync( + method, + sessionId, + string.IsNullOrWhiteSpace(parametersJson) + ? null + : writer => BrowserLogsCdpProtocol.WriteRawCommandParameters(writer, parametersJson), + BrowserLogsCdpProtocol.ParseRawCommandResponse, + cancellationToken); + } + public async ValueTask DisposeAsync() { _disposeCts.Cancel(); diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnectionMultiplexer.cs b/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnectionMultiplexer.cs index 5a5515ddfed..f4e6d741ea8 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnectionMultiplexer.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsCdpConnectionMultiplexer.cs @@ -176,10 +176,10 @@ public Task EnablePageInstrumentationAsync(string sessionId, CancellationToken c return owner._innerConnection.EnablePageInstrumentationAsync(sessionId, cancellationToken); } - public Task CaptureScreenshotAsync(string sessionId, CancellationToken cancellationToken) + public Task CaptureScreenshotAsync(string sessionId, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { ThrowIfDisposed(); - return owner._innerConnection.CaptureScreenshotAsync(sessionId, cancellationToken); + return owner._innerConnection.CaptureScreenshotAsync(sessionId, options, cancellationToken); } public Task NavigateAsync(string sessionId, Uri url, CancellationToken cancellationToken) @@ -188,6 +188,18 @@ public Task NavigateAsync(string sessionId, Uri url, Canc return owner._innerConnection.NavigateAsync(sessionId, url, cancellationToken); } + public Task EvaluateAsync(string sessionId, string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + return owner._innerConnection.EvaluateAsync(sessionId, expression, timeout, cancellationToken); + } + + public Task SendRawCommandAsync(string? sessionId, string method, string? parametersJson, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + return owner._innerConnection.SendRawCommandAsync(sessionId, method, parametersJson, cancellationToken); + } + public async ValueTask DisposeAsync() { if (Interlocked.Exchange(ref _disposed, 1) != 0) diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsCdpProtocol.cs b/src/Aspire.Hosting.Browsers/BrowserLogsCdpProtocol.cs index f91b4644975..2415992e531 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsCdpProtocol.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsCdpProtocol.cs @@ -44,6 +44,7 @@ internal static class BrowserLogsCdpProtocol internal const string PageNavigateMethod = "Page.navigate"; internal const string RuntimeConsoleApiCalledMethod = "Runtime.consoleAPICalled"; internal const string RuntimeEnableMethod = "Runtime.enable"; + internal const string RuntimeEvaluateMethod = "Runtime.evaluate"; internal const string RuntimeExceptionThrownMethod = "Runtime.exceptionThrown"; internal const string TargetAttachToTargetMethod = "Target.attachToTarget"; internal const string TargetCloseTargetMethod = "Target.closeTarget"; @@ -208,6 +209,58 @@ internal static BrowserLogsCaptureScreenshotResult ParseCaptureScreenshotRespons return result; } + internal static BrowserLogsRuntimeEvaluateResult ParseRuntimeEvaluateResponse(ReadOnlySpan framePayload) + { + // Expected Runtime.evaluate response shape: + // { "id": 1, "result": { "result": { "type": "string", "value": "{...}" }, "exceptionDetails": { ... } } } + var envelope = DeserializeFrame(framePayload, BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsRuntimeEvaluateResponseEnvelope); + ThrowIfProtocolError(envelope.Error); + + var result = envelope.Result ?? throw new InvalidOperationException("Tracked browser script evaluation did not return a result payload."); + if (result.ExceptionDetails is { } exceptionDetails) + { + throw new InvalidOperationException(FormatRuntimeException(exceptionDetails)); + } + + return result; + } + + internal static string ParseRawCommandResponse(ReadOnlySpan framePayload) + { + // Expected generic CDP response shape: + // { "id": 1, "result": { ... } } or { "id": 1, "error": { "code": -32601, "message": "..." } } + using var document = JsonDocument.Parse(framePayload.ToArray()); + var root = document.RootElement; + + if (root.TryGetProperty("error", out var errorElement)) + { + ThrowIfProtocolError(new BrowserLogsCdpProtocolError + { + Code = errorElement.TryGetProperty("code", out var codeElement) && codeElement.TryGetInt32(out var code) ? code : null, + Message = errorElement.TryGetProperty("message", out var messageElement) && messageElement.ValueKind == JsonValueKind.String ? messageElement.GetString() : null + }); + } + + return root.TryGetProperty("result", out var resultElement) + ? JsonSerializer.Serialize(resultElement) + : "{}"; + } + + internal static void WriteRawCommandParameters(Utf8JsonWriter writer, string parametersJson) + { + using var document = JsonDocument.Parse(parametersJson); + var root = document.RootElement; + if (root.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException("CDP command parameters must be a JSON object."); + } + + foreach (var property in root.EnumerateObject()) + { + property.WriteTo(writer); + } + } + internal static string DescribeFrame(ReadOnlySpan framePayload, int maxLength = 512) { var text = Encoding.UTF8.GetString(framePayload); @@ -321,6 +374,22 @@ private static void ThrowIfProtocolError(BrowserLogsCdpProtocolError? error) throw new InvalidOperationException(message); } + + private static string FormatRuntimeException(BrowserLogsExceptionDetails exceptionDetails) + { + var message = !string.IsNullOrWhiteSpace(exceptionDetails.Exception?.Description) + ? exceptionDetails.Exception.Description + : !string.IsNullOrWhiteSpace(exceptionDetails.Text) + ? exceptionDetails.Text + : "Tracked browser script evaluation failed."; + + if (exceptionDetails.Url is { Length: > 0 } url) + { + return $"{message} ({url}:{exceptionDetails.LineNumber ?? 0}:{exceptionDetails.ColumnNumber ?? 0})."; + } + + return message; + } } internal readonly record struct BrowserLogsCdpProtocolMessageHeader(long? Id, string? Method, string? SessionId); @@ -437,6 +506,27 @@ internal sealed class BrowserLogsCaptureScreenshotResult public string? Data { get; init; } } +internal sealed class BrowserLogsRuntimeEvaluateResponseEnvelope +{ + [JsonPropertyName("error")] + public BrowserLogsCdpProtocolError? Error { get; init; } + + [JsonPropertyName("id")] + public long Id { get; init; } + + [JsonPropertyName("result")] + public BrowserLogsRuntimeEvaluateResult? Result { get; init; } +} + +internal sealed class BrowserLogsRuntimeEvaluateResult +{ + [JsonPropertyName("exceptionDetails")] + public BrowserLogsExceptionDetails? ExceptionDetails { get; init; } + + [JsonPropertyName("result")] + public BrowserLogsCdpProtocolRemoteObject? Result { get; init; } +} + internal interface IBrowserLogsEventEnvelope where TParameters : class { @@ -945,6 +1035,7 @@ private static BrowserLogsCdpProtocolValue ReadValue(ref Utf8JsonReader reader, [JsonSerializable(typeof(BrowserLogsLogEntryAddedEnvelope))] [JsonSerializable(typeof(BrowserLogsRequestWillBeSentEnvelope))] [JsonSerializable(typeof(BrowserLogsResponseReceivedEnvelope))] +[JsonSerializable(typeof(BrowserLogsRuntimeEvaluateResponseEnvelope))] [JsonSerializable(typeof(BrowserLogsTargetCrashedEnvelope))] [JsonSerializable(typeof(BrowserLogsTargetDestroyedEnvelope))] internal sealed partial class BrowserLogsCdpProtocolJsonContext : JsonSerializerContext; diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationManager.cs b/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationManager.cs index 33793277d94..68598ca0a6d 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationManager.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationManager.cs @@ -3,7 +3,7 @@ #pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only #pragma warning disable ASPIREUSERSECRETS001 // Type is for evaluation purposes only -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only using System.Globalization; using Aspire.Hosting.Browsers.Resources; @@ -31,7 +31,7 @@ internal sealed class BrowserLogsConfigurationManager( private const string GlobalScopeValue = "global"; private const string BrowserDefaultProfileValue = "__aspire_browser_default__"; - public async Task ConfigureAsync(BrowserLogsResource resource, CancellationToken cancellationToken) + public async Task ConfigureAsync(BrowserAutomationResource resource, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(resource); @@ -64,9 +64,9 @@ public async Task ConfigureAsync(BrowserLogsResource resou var resolvedConfigurations = ResolveEffectiveConfigurations(resource, selected); Apply(resource, selected); - foreach (var (browserLogsResource, browserConfiguration) in resolvedConfigurations) + foreach (var (browserAutomationResource, browserConfiguration) in resolvedConfigurations) { - await PublishConfigurationSnapshotAsync(browserLogsResource, browserConfiguration).ConfigureAwait(false); + await PublishConfigurationSnapshotAsync(browserAutomationResource, browserConfiguration).ConfigureAwait(false); } var scopeName = selected.Scope == BrowserLogsConfigurationScope.Resource @@ -86,7 +86,7 @@ public async Task ConfigureAsync(BrowserLogsResource resou }; } - private List CreateInputs(BrowserLogsResource resource, BrowserConfiguration currentConfiguration) + private List CreateInputs(BrowserAutomationResource resource, BrowserConfiguration currentConfiguration) { var scopeInput = new InteractionInput { @@ -261,7 +261,7 @@ private static string FormatProfileOption(ChromiumBrowserProfile profile) return profile.DirectoryName; } - private Task ValidateInputsAsync(BrowserLogsResource resource, InputsDialogValidationContext context) + private Task ValidateInputsAsync(BrowserAutomationResource resource, InputsDialogValidationContext context) { var inputs = context.Inputs; var browser = inputs[BrowserInputName]; @@ -299,7 +299,7 @@ private Task ValidateInputsAsync(BrowserLogsResource resource, InputsDialogValid { try { - // Resolve the final effective configuration so explicit WithBrowserLogs values are validated before + // Resolve the final effective configuration so explicit WithBrowserAutomation values are validated before // applying runtime settings or mutating user secrets. _ = ResolveEffectiveConfigurations(resource, BrowserLogsConfigurationSelection.FromInputs(inputs)); } @@ -312,13 +312,13 @@ private Task ValidateInputsAsync(BrowserLogsResource resource, InputsDialogValid return Task.CompletedTask; } - private List<(BrowserLogsResource Resource, BrowserConfiguration Configuration)> ResolveEffectiveConfigurations( - BrowserLogsResource commandResource, + private List<(BrowserAutomationResource Resource, BrowserConfiguration Configuration)> ResolveEffectiveConfigurations( + BrowserAutomationResource commandResource, BrowserLogsConfigurationSelection selected) { var selectedConfiguration = ToBrowserConfiguration(selected); - IEnumerable resources = selected.Scope == BrowserLogsConfigurationScope.Global - ? applicationModel.Resources.OfType() + IEnumerable resources = selected.Scope == BrowserLogsConfigurationScope.Global + ? applicationModel.Resources.OfType() : [commandResource]; return [.. resources.Select(resource => @@ -326,8 +326,8 @@ private Task ValidateInputsAsync(BrowserLogsResource resource, InputsDialogValid } private BrowserConfiguration ResolveEffectiveConfiguration( - BrowserLogsResource resource, - BrowserLogsResource commandResource, + BrowserAutomationResource resource, + BrowserAutomationResource commandResource, BrowserLogsConfigurationSelection selected, BrowserConfiguration selectedConfiguration) { @@ -358,21 +358,21 @@ private BrowserConfiguration ToBrowserConfiguration(BrowserLogsConfigurationSele configuration["AppHost:PathSha256"]); } - private void Apply(BrowserLogsResource resource, BrowserLogsConfigurationSelection selected) + private void Apply(BrowserAutomationResource resource, BrowserLogsConfigurationSelection selected) { var configurationPrefix = selected.Scope == BrowserLogsConfigurationScope.Resource - ? $"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{resource.ParentResource.Name}" - : BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName; + ? $"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{resource.ParentResource.Name}" + : BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName; if (selected.SaveToUserSecrets) { // IUserSecretsManager persists one key at a time, so a later failure can leave earlier secret mutations // on disk. Only update the runtime store after every requested mutation succeeds, so the current AppHost // never observes a partial save. - SaveValue($"{configurationPrefix}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}", selected.Browser); - SaveValue($"{configurationPrefix}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}", selected.UserDataMode.ToString()); + SaveValue($"{configurationPrefix}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}", selected.Browser); + SaveValue($"{configurationPrefix}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}", selected.UserDataMode.ToString()); - var profileKey = $"{configurationPrefix}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"; + var profileKey = $"{configurationPrefix}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"; if (selected.Profile is { } profile) { SaveValue(profileKey, profile); @@ -410,7 +410,7 @@ private void DeleteValue(string key) } } - private Task PublishConfigurationSnapshotAsync(BrowserLogsResource resource, BrowserConfiguration browserConfiguration) + private Task PublishConfigurationSnapshotAsync(BrowserAutomationResource resource, BrowserConfiguration browserConfiguration) { return resourceNotificationService.PublishUpdateAsync(resource, snapshot => snapshot with { @@ -423,12 +423,12 @@ private static System.Collections.Immutable.ImmutableArray RemoveProperty( diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationStore.cs b/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationStore.cs index 2b10afab8a7..bd0f66f9a51 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationStore.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsConfigurationStore.cs @@ -3,7 +3,7 @@ namespace Aspire.Hosting; -// Stores browser-log configuration chosen from the dashboard for the current AppHost process. User secrets persist the +// Stores browser automation configuration chosen from the dashboard for the current AppHost process. User secrets persist the // same values for the next run, but the store makes the next command execution use the new values immediately without // depending on configuration reload timing. internal sealed class BrowserLogsConfigurationStore diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsRunningSession.cs b/src/Aspire.Hosting.Browsers/BrowserLogsRunningSession.cs index f7bab646606..2d7c92d6b14 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsRunningSession.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsRunningSession.cs @@ -25,7 +25,13 @@ internal interface IBrowserLogsRunningSession Task StartCompletionObserver(Func onCompleted); - Task CaptureScreenshotAsync(CancellationToken cancellationToken); + Task CaptureScreenshotAsync(BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken); + + Task NavigateAsync(Uri url, CancellationToken cancellationToken); + + Task EvaluateJsonAsync(string expression, TimeSpan? timeout, CancellationToken cancellationToken); + + Task SendCdpCommandJsonAsync(string method, string? parametersJson, string session, CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken); } @@ -173,10 +179,10 @@ public Task StartCompletionObserver(Func onCompleted) return ObserveCompletionAsync(onCompleted); } - public async Task CaptureScreenshotAsync(CancellationToken cancellationToken) + public async Task CaptureScreenshotAsync(BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { var pageSession = _pageSession ?? throw new InvalidOperationException("Browser page session is not available."); - var result = await pageSession.CaptureScreenshotAsync(cancellationToken).ConfigureAwait(false); + var result = await pageSession.CaptureScreenshotAsync(options, cancellationToken).ConfigureAwait(false); var data = result.Data ?? throw new InvalidOperationException("Tracked browser screenshot capture returned no image data."); @@ -190,11 +196,29 @@ public async Task CaptureScreenshotAsync(CancellationToken cancellationT } } + public async Task NavigateAsync(Uri url, CancellationToken cancellationToken) + { + var pageSession = _pageSession ?? throw new InvalidOperationException("Browser page session is not available."); + await pageSession.NavigateAsync(url, cancellationToken).ConfigureAwait(false); + } + + public async Task EvaluateJsonAsync(string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + var pageSession = _pageSession ?? throw new InvalidOperationException("Browser page session is not available."); + return await pageSession.EvaluateJsonAsync(expression, timeout, cancellationToken).ConfigureAwait(false); + } + + public async Task SendCdpCommandJsonAsync(string method, string? parametersJson, string session, CancellationToken cancellationToken) + { + var pageSession = _pageSession ?? throw new InvalidOperationException("Browser page session is not available."); + return await pageSession.SendCdpCommandJsonAsync(method, parametersJson, session, cancellationToken).ConfigureAwait(false); + } + public async Task StopAsync(CancellationToken cancellationToken) { try { _stopCts.Cancel(); } catch (ObjectDisposedException) { } - // Stopping a dashboard browser-log session should close only the page target it created. The shared browser + // Stopping a dashboard browser automation session should close only the page target it created. The shared browser // process/window is released through the lease and may stay alive while other resource sessions are still active. await DisposePageSessionAsync().ConfigureAwait(false); await DisposeBrowserHostLeaseAsync().ConfigureAwait(false); diff --git a/src/Aspire.Hosting.Browsers/BrowserLogsSessionManager.cs b/src/Aspire.Hosting.Browsers/BrowserLogsSessionManager.cs index 5646c0a421a..9092695a7c1 100644 --- a/src/Aspire.Hosting.Browsers/BrowserLogsSessionManager.cs +++ b/src/Aspire.Hosting.Browsers/BrowserLogsSessionManager.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only using System.Collections.Concurrent; using System.Collections.Immutable; @@ -13,7 +13,7 @@ namespace Aspire.Hosting; -// Coordinates browser-log commands with dashboard resource state. The running session owns CDP capture; this manager +// Coordinates browser automation commands with dashboard resource state. The running session owns CDP capture; this manager // owns session ids, resource logs, health reports, and snapshot properties that make failures diagnosable in the dashboard. internal sealed class BrowserLogsSessionManager : IBrowserLogsSessionManager, IAsyncDisposable { @@ -59,7 +59,7 @@ internal BrowserLogsSessionManager( _sessionFactory = sessionFactory; } - public async Task StartSessionAsync(BrowserLogsResource resource, BrowserConfiguration configuration, string resourceName, Uri url, CancellationToken cancellationToken) + public async Task StartSessionAsync(BrowserAutomationResource resource, BrowserConfiguration configuration, string resourceName, Uri url, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(resource); ArgumentNullException.ThrowIfNull(configuration.Browser); @@ -68,7 +68,7 @@ public async Task StartSessionAsync(BrowserLogsResource resource, BrowserConfigu ThrowIfDisposing(); var resourceState = _resourceStates.GetOrAdd(resourceName, static _ => new ResourceSessionState()); - // Dashboard commands can start/stop browser-log sessions for the same resource while previous targets are still + // Dashboard commands can start/stop browser automation sessions for the same resource while previous targets are still // completing. Serialize per resource so session ids, health reports, and properties describe the same observed // set of browser targets. await resourceState.Lock.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -181,7 +181,7 @@ void ThrowIfDisposing() } } - public async Task CaptureScreenshotAsync(string resourceName, CancellationToken cancellationToken) + public async Task CaptureScreenshotAsync(string resourceName, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrWhiteSpace(resourceName); ThrowIfDisposing(); @@ -211,13 +211,13 @@ public async Task CaptureScreenshotAsync(str resourceState.Lock.Release(); } - var screenshotBytes = await activeSession.Session.CaptureScreenshotAsync(cancellationToken).ConfigureAwait(false); + var screenshotBytes = await activeSession.Session.CaptureScreenshotAsync(options, cancellationToken).ConfigureAwait(false); var artifact = await _artifactWriter.WriteArtifactAsync( activeSession.AppHostKey, resourceName, artifactType: "screenshot", - fileExtension: ".png", - mimeType: "image/png", + fileExtension: options.FileExtension, + mimeType: options.MimeType, content: screenshotBytes, cancellationToken).ConfigureAwait(false); @@ -249,6 +249,527 @@ void ThrowIfDisposing() } } + public async Task GetPageSnapshotAsync(string resourceName, int maxElements, int maxTextLength, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "inspect", cancellationToken).ConfigureAwait(false); + var expression = BrowserLogsBrowserAutomationScripts.CreateSnapshotExpression(maxElements, maxTextLength); + return await activeSession.Session.EvaluateJsonAsync(expression, timeout: null, cancellationToken).ConfigureAwait(false); + } + + public async Task GetAsync(string resourceName, string property, string? selector, string? name, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(property); + + var activeSession = await GetActiveSessionAsync(resourceName, "get", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateGetExpression(property, selector, name), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task IsAsync(string resourceName, string state, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(state); + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "is", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateIsExpression(state, selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task FindAsync(string resourceName, string kind, string value, string? name, int index, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(kind); + ArgumentException.ThrowIfNullOrWhiteSpace(value); + + var activeSession = await GetActiveSessionAsync(resourceName, "find", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateFindExpression(kind, value, name, index), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task HighlightAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "highlight", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateHighlightExpression(selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task EvaluateAsync(string resourceName, string expression, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + + var activeSession = await GetActiveSessionAsync(resourceName, "evaluate", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateEvaluateExpression(expression), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task CookiesAsync(string resourceName, string action, string? name, string? value, string? domain, string? path, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(action); + + var activeSession = await GetActiveSessionAsync(resourceName, "cookies", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateCookiesExpression(action, name, value, domain, path), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task StorageAsync(string resourceName, string area, string action, string? key, string? value, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(area); + ArgumentException.ThrowIfNullOrWhiteSpace(action); + + var activeSession = await GetActiveSessionAsync(resourceName, "storage", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateStorageExpression(area, action, key, value), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task StateAsync(string resourceName, string action, string? state, bool clearExisting, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(action); + + var activeSession = await GetActiveSessionAsync(resourceName, "state", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateStateExpression(action, state, clearExisting), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task CdpAsync(string resourceName, string method, string? parametersJson, string session, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(method); + ArgumentException.ThrowIfNullOrWhiteSpace(session); + + var activeSession = await GetActiveSessionAsync(resourceName, "send CDP command", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.SendCdpCommandJsonAsync(method, parametersJson, session, cancellationToken).ConfigureAwait(false); + } + + public async Task TabsAsync(string resourceName, string action, string? url, string? targetId, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(action); + + var activeSession = await GetActiveSessionAsync(resourceName, "manage tabs", cancellationToken).ConfigureAwait(false); + var resultJson = action switch + { + "list" => await activeSession.Session.SendCdpCommandJsonAsync("Target.getTargets", parametersJson: null, "browser", cancellationToken).ConfigureAwait(false), + "open" => await activeSession.Session.SendCdpCommandJsonAsync( + "Target.createTarget", + JsonSerializer.Serialize(new { url = GetRequiredUrlText(url) }), + "browser", + cancellationToken).ConfigureAwait(false), + "close" => await activeSession.Session.SendCdpCommandJsonAsync( + "Target.closeTarget", + JsonSerializer.Serialize(new { targetId = GetRequiredTargetId(targetId) }), + "browser", + cancellationToken).ConfigureAwait(false), + _ => throw new InvalidOperationException("Tab action must be 'list', 'open', or 'close'.") + }; + + return CreateBrowserCommandEnvelope("tabs", resultJson); + } + + public async Task FramesAsync(string resourceName, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "list frames", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateFramesExpression(), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task DialogAsync(string resourceName, string action, string? promptText, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(action); + + var accept = action switch + { + "accept" => true, + "dismiss" => false, + _ => throw new InvalidOperationException("Dialog action must be 'accept' or 'dismiss'.") + }; + + var activeSession = await GetActiveSessionAsync(resourceName, "handle dialog", cancellationToken).ConfigureAwait(false); + var parametersJson = promptText is null + ? JsonSerializer.Serialize(new { accept }) + : JsonSerializer.Serialize(new { accept, promptText }); + var resultJson = await activeSession.Session.SendCdpCommandJsonAsync("Page.handleJavaScriptDialog", parametersJson, "page", cancellationToken).ConfigureAwait(false); + + return CreateBrowserCommandEnvelope("dialog", resultJson); + } + + public async Task DownloadsAsync(string resourceName, string behavior, string? downloadPath, bool eventsEnabled, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(behavior); + + if (behavior is not ("allow" or "deny" or "default" or "allowAndName")) + { + throw new InvalidOperationException("Download behavior must be 'allow', 'allowAndName', 'deny', or 'default'."); + } + + if ((behavior is "allow" or "allowAndName") && string.IsNullOrWhiteSpace(downloadPath)) + { + throw new InvalidOperationException("A download path is required when download behavior is 'allow' or 'allowAndName'."); + } + + var parametersJson = string.IsNullOrWhiteSpace(downloadPath) + ? JsonSerializer.Serialize(new { behavior, eventsEnabled }) + : JsonSerializer.Serialize(new { behavior, downloadPath, eventsEnabled }); + + var activeSession = await GetActiveSessionAsync(resourceName, "configure downloads", cancellationToken).ConfigureAwait(false); + var resultJson = await activeSession.Session.SendCdpCommandJsonAsync("Browser.setDownloadBehavior", parametersJson, "browser", cancellationToken).ConfigureAwait(false); + + return CreateBrowserCommandEnvelope("downloads", resultJson); + } + + public async Task UploadAsync(string resourceName, string selector, string files, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + ArgumentException.ThrowIfNullOrWhiteSpace(files); + + var filePaths = ParseFilePaths(files); + if (filePaths.Length == 0) + { + throw new InvalidOperationException("At least one file path is required."); + } + + var activeSession = await GetActiveSessionAsync(resourceName, "upload files", cancellationToken).ConfigureAwait(false); + var documentJson = await activeSession.Session.SendCdpCommandJsonAsync("DOM.getDocument", parametersJson: null, "page", cancellationToken).ConfigureAwait(false); + var rootNodeId = GetIntegerProperty(documentJson, "root", "nodeId"); + var queryJson = await activeSession.Session.SendCdpCommandJsonAsync( + "DOM.querySelector", + JsonSerializer.Serialize(new { nodeId = rootNodeId, selector }), + "page", + cancellationToken).ConfigureAwait(false); + var nodeId = GetIntegerProperty(queryJson, "nodeId"); + if (nodeId == 0) + { + throw new InvalidOperationException($"Element '{selector}' was not found."); + } + + var resultJson = await activeSession.Session.SendCdpCommandJsonAsync( + "DOM.setFileInputFiles", + JsonSerializer.Serialize(new { files = filePaths, nodeId }), + "page", + cancellationToken).ConfigureAwait(false); + + return JsonSerializer.Serialize(new + { + action = "upload", + selector, + files = filePaths, + result = JsonSerializer.Deserialize(resultJson) + }); + } + + public async Task GetUrlAsync(string resourceName, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "get URL", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateUrlExpression(), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task GoBackAsync(string resourceName, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "go back", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateHistoryNavigationExpression("back"), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task GoForwardAsync(string resourceName, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "go forward", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateHistoryNavigationExpression("forward"), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task ReloadAsync(string resourceName, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "reload", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateHistoryNavigationExpression("reload"), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task NavigateAsync(BrowserAutomationResource resource, string resourceName, Uri url, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(resource); + ArgumentNullException.ThrowIfNull(url); + + var activeSession = await GetActiveSessionAsync(resourceName, "navigate", cancellationToken).ConfigureAwait(false); + await activeSession.Session.NavigateAsync(url, cancellationToken).ConfigureAwait(false); + await UpdateActiveSessionTargetUrlAsync(resource, resourceName, activeSession.SessionId, url, cancellationToken).ConfigureAwait(false); + + return JsonSerializer.Serialize( + new BrowserLogsNavigateCommandResult( + activeSession.SessionId, + activeSession.Browser, + activeSession.TargetId, + url.ToString()), + s_browserSessionPropertyJsonOptions); + } + + public async Task ClickAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "click", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateClickExpression(selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task DoubleClickAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "double click", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateDoubleClickExpression(selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task FillAsync(string resourceName, string selector, string value, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + ArgumentNullException.ThrowIfNull(value); + + var activeSession = await GetActiveSessionAsync(resourceName, "fill", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateFillExpression(selector, value), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task CheckAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "check", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateCheckExpression(selector, isChecked: true), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task UncheckAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "uncheck", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateCheckExpression(selector, isChecked: false), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task FocusAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "focus", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateFocusExpression(selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task TypeAsync(string resourceName, string selector, string text, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + ArgumentNullException.ThrowIfNull(text); + + var activeSession = await GetActiveSessionAsync(resourceName, "type text", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateTypeExpression(selector, text), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task PressAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(key); + + var activeSession = await GetActiveSessionAsync(resourceName, "press keys", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreatePressExpression(selector, key), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task HoverAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "hover", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateHoverExpression(selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task SelectAsync(string resourceName, string selector, string value, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + ArgumentNullException.ThrowIfNull(value); + + var activeSession = await GetActiveSessionAsync(resourceName, "select", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateSelectExpression(selector, value), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task ScrollAsync(string resourceName, string? selector, int deltaX, int deltaY, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "scroll", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateScrollExpression(selector, deltaX, deltaY), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task ScrollIntoViewAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + + var activeSession = await GetActiveSessionAsync(resourceName, "scroll into view", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateScrollIntoViewExpression(selector), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task KeyDownAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(key); + + var activeSession = await GetActiveSessionAsync(resourceName, "key down", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateKeyEventExpression("keydown", selector, key), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task KeyUpAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(key); + + var activeSession = await GetActiveSessionAsync(resourceName, "key up", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateKeyEventExpression("keyup", selector, key), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task MouseAsync(string resourceName, string action, int x, int y, string? button, int deltaX, int deltaY, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(action); + + var activeSession = await GetActiveSessionAsync(resourceName, "mouse input", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateMouseExpression(action, x, y, button, deltaX, deltaY), + timeout: null, + cancellationToken).ConfigureAwait(false); + } + + public async Task WaitForAsync(string resourceName, string? selector, string? text, int timeoutMilliseconds, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(selector) && string.IsNullOrWhiteSpace(text)) + { + throw new ArgumentException("Provide a selector, text, or both when waiting in the browser."); + } + + var activeSession = await GetActiveSessionAsync(resourceName, "wait", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateWaitForExpression(selector, text, timeoutMilliseconds), + CreateEvaluationTimeout(timeoutMilliseconds), + cancellationToken).ConfigureAwait(false); + } + + public async Task WaitForUrlAsync(string resourceName, string url, string match, int timeoutMilliseconds, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(url); + ArgumentException.ThrowIfNullOrWhiteSpace(match); + + var activeSession = await GetActiveSessionAsync(resourceName, "wait for URL", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateWaitForUrlExpression(url, match, timeoutMilliseconds), + CreateEvaluationTimeout(timeoutMilliseconds), + cancellationToken).ConfigureAwait(false); + } + + public async Task WaitForLoadStateAsync(string resourceName, string state, int timeoutMilliseconds, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(state); + + var activeSession = await GetActiveSessionAsync(resourceName, "wait for load state", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateWaitForLoadStateExpression(state, timeoutMilliseconds), + CreateEvaluationTimeout(timeoutMilliseconds), + cancellationToken).ConfigureAwait(false); + } + + public async Task WaitForElementStateAsync(string resourceName, string selector, string state, int timeoutMilliseconds, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(selector); + ArgumentException.ThrowIfNullOrWhiteSpace(state); + + var activeSession = await GetActiveSessionAsync(resourceName, "wait for element state", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateWaitForElementStateExpression(selector, state, timeoutMilliseconds), + CreateEvaluationTimeout(timeoutMilliseconds), + cancellationToken).ConfigureAwait(false); + } + + public async Task WaitForFunctionAsync(string resourceName, string function, int timeoutMilliseconds, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(function); + + var activeSession = await GetActiveSessionAsync(resourceName, "wait for function", cancellationToken).ConfigureAwait(false); + return await activeSession.Session.EvaluateJsonAsync( + BrowserLogsBrowserAutomationScripts.CreateWaitForFunctionExpression(function, timeoutMilliseconds), + CreateEvaluationTimeout(timeoutMilliseconds), + cancellationToken).ConfigureAwait(false); + } + + public async Task CloseActiveSessionAsync(string resourceName, CancellationToken cancellationToken) + { + var activeSession = await GetActiveSessionAsync(resourceName, "close", cancellationToken).ConfigureAwait(false); + await activeSession.Session.StopAsync(cancellationToken).ConfigureAwait(false); + + return JsonSerializer.Serialize( + new BrowserLogsCloseBrowserCommandResult( + activeSession.SessionId, + activeSession.Browser, + activeSession.BrowserExecutable, + activeSession.BrowserHostOwnership, + activeSession.ProcessId, + activeSession.TargetId, + activeSession.TargetUrl.ToString()), + s_browserSessionPropertyJsonOptions); + } + public async ValueTask DisposeAsync() { Interlocked.Exchange(ref _disposing, 1); @@ -312,7 +833,7 @@ public async ValueTask DisposeAsync() } private async Task HandleSessionCompletedAsync( - BrowserLogsResource resource, + BrowserAutomationResource resource, string resourceName, ResourceSessionState resourceState, string sessionId, @@ -368,7 +889,7 @@ await PublishResourceSnapshotAsync( } private Task PublishResourceSnapshotAsync( - BrowserLogsResource resource, + BrowserAutomationResource resource, string resourceName, ResourceSessionState resourceState, string stateText, @@ -424,7 +945,7 @@ private ImmutableArray GetHealthReports(ResourceSessionSta else if (resourceState.LastError is not null) { reports.Add(new HealthReportSnapshot( - BrowserLogsBuilderExtensions.LastErrorPropertyName, + BrowserAutomationBuilderExtensions.LastErrorPropertyName, HealthStatus.Unhealthy, resourceState.LastError, null) @@ -438,34 +959,34 @@ private ImmutableArray GetHealthReports(ResourceSessionSta private static IEnumerable GetPropertyUpdates(ResourceSessionState resourceState) { - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, resourceState.ActiveSessions.Count); - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, FormatActiveSessions(resourceState.ActiveSessions.Values)); - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.BrowserSessionsPropertyName, FormatBrowserSessions(resourceState.ActiveSessions.Values)); - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.TotalSessionsLaunchedPropertyName, resourceState.TotalSessionsLaunched); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, resourceState.ActiveSessions.Count); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, FormatActiveSessions(resourceState.ActiveSessions.Values)); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.BrowserSessionsPropertyName, FormatBrowserSessions(resourceState.ActiveSessions.Values)); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.TotalSessionsLaunchedPropertyName, resourceState.TotalSessionsLaunched); if (resourceState.LastSessionId is not null) { - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.LastSessionPropertyName, resourceState.LastSessionId); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.LastSessionPropertyName, resourceState.LastSessionId); } if (resourceState.LastTargetUrl is not null) { - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.TargetUrlPropertyName, resourceState.LastTargetUrl); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.TargetUrlPropertyName, resourceState.LastTargetUrl); } if (resourceState.LastBrowserExecutable is not null) { - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, resourceState.LastBrowserExecutable); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, resourceState.LastBrowserExecutable); } if (resourceState.LastBrowserHostOwnership is not null) { - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.BrowserHostOwnershipPropertyName, resourceState.LastBrowserHostOwnership); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.BrowserHostOwnershipPropertyName, resourceState.LastBrowserHostOwnership); } if (resourceState.LastError is not null) { - yield return new ResourcePropertySnapshot(BrowserLogsBuilderExtensions.LastErrorPropertyName, resourceState.LastError); + yield return new ResourcePropertySnapshot(BrowserAutomationBuilderExtensions.LastErrorPropertyName, resourceState.LastError); } } @@ -475,24 +996,24 @@ private static ImmutableArray UpdateProperties( IEnumerable propertyUpdates) { properties = resourceState.LastBrowser is not null - ? properties.SetResourceProperty(BrowserLogsBuilderExtensions.BrowserPropertyName, resourceState.LastBrowser) - : RemoveProperty(properties, BrowserLogsBuilderExtensions.BrowserPropertyName); + ? properties.SetResourceProperty(BrowserAutomationBuilderExtensions.BrowserPropertyName, resourceState.LastBrowser) + : RemoveProperty(properties, BrowserAutomationBuilderExtensions.BrowserPropertyName); properties = resourceState.LastBrowserExecutable is not null - ? properties.SetResourceProperty(BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, resourceState.LastBrowserExecutable) - : RemoveProperty(properties, BrowserLogsBuilderExtensions.BrowserExecutablePropertyName); + ? properties.SetResourceProperty(BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, resourceState.LastBrowserExecutable) + : RemoveProperty(properties, BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName); properties = resourceState.LastBrowserHostOwnership is not null - ? properties.SetResourceProperty(BrowserLogsBuilderExtensions.BrowserHostOwnershipPropertyName, resourceState.LastBrowserHostOwnership) - : RemoveProperty(properties, BrowserLogsBuilderExtensions.BrowserHostOwnershipPropertyName); + ? properties.SetResourceProperty(BrowserAutomationBuilderExtensions.BrowserHostOwnershipPropertyName, resourceState.LastBrowserHostOwnership) + : RemoveProperty(properties, BrowserAutomationBuilderExtensions.BrowserHostOwnershipPropertyName); properties = resourceState.LastError is not null - ? properties.SetResourceProperty(BrowserLogsBuilderExtensions.LastErrorPropertyName, resourceState.LastError) - : RemoveProperty(properties, BrowserLogsBuilderExtensions.LastErrorPropertyName); + ? properties.SetResourceProperty(BrowserAutomationBuilderExtensions.LastErrorPropertyName, resourceState.LastError) + : RemoveProperty(properties, BrowserAutomationBuilderExtensions.LastErrorPropertyName); properties = resourceState.LastProfile is not null - ? properties.SetResourceProperty(BrowserLogsBuilderExtensions.ProfilePropertyName, resourceState.LastProfile) - : RemoveProperty(properties, BrowserLogsBuilderExtensions.ProfilePropertyName); + ? properties.SetResourceProperty(BrowserAutomationBuilderExtensions.ProfilePropertyName, resourceState.LastProfile) + : RemoveProperty(properties, BrowserAutomationBuilderExtensions.ProfilePropertyName); return properties.SetResourcePropertyRange(propertyUpdates); } @@ -510,6 +1031,128 @@ private static ImmutableArray RemoveProperty(Immutable return properties; } + private async Task GetActiveSessionAsync(string resourceName, string action, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(resourceName); + ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposing) != 0, this); + + var resourceState = _resourceStates.GetOrAdd(resourceName, static _ => new ResourceSessionState()); + await resourceState.Lock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposing) != 0, this); + + var activeSession = SelectActiveSession(resourceState); + return activeSession ?? throw new InvalidOperationException($"No active tracked browser session is available to {action}."); + } + finally + { + resourceState.Lock.Release(); + } + } + + private async Task UpdateActiveSessionTargetUrlAsync(BrowserAutomationResource resource, string resourceName, string sessionId, Uri targetUrl, CancellationToken cancellationToken) + { + var resourceState = _resourceStates.GetOrAdd(resourceName, static _ => new ResourceSessionState()); + await resourceState.Lock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + if (resourceState.ActiveSessions.TryGetValue(sessionId, out var activeSession)) + { + resourceState.ActiveSessions[sessionId] = activeSession with { TargetUrl = targetUrl }; + resourceState.LastTargetUrl = targetUrl.ToString(); + + await PublishResourceSnapshotAsync( + resource, + resourceName, + resourceState, + stateText: KnownResourceStates.Running, + stateStyle: KnownResourceStateStyles.Success, + pendingSession: null, + stopTimeStamp: null, + exitCode: null).ConfigureAwait(false); + } + } + finally + { + resourceState.Lock.Release(); + } + } + + private static string GetRequiredUrlText(string? url) + { + if (string.IsNullOrWhiteSpace(url)) + { + throw new InvalidOperationException("A URL is required for the tab open action."); + } + + return url; + } + + private static string GetRequiredTargetId(string? targetId) + { + if (string.IsNullOrWhiteSpace(targetId)) + { + throw new InvalidOperationException("A target id is required for the tab close action."); + } + + return targetId; + } + + private static string[] ParseFilePaths(string files) + { + var trimmed = files.Trim(); + if (!trimmed.StartsWith("[", StringComparison.Ordinal)) + { + return [trimmed]; + } + + return JsonSerializer.Deserialize(trimmed) + ?? throw new InvalidOperationException("File paths JSON must be an array of strings."); + } + + private static int GetIntegerProperty(string json, params string[] propertyPath) + { + using var document = JsonDocument.Parse(json); + var current = document.RootElement; + foreach (var propertyName in propertyPath) + { + current = current.TryGetProperty(propertyName, out var property) + ? property + : throw new InvalidOperationException($"CDP response did not contain '{string.Join(".", propertyPath)}'."); + } + + return current.TryGetInt32(out var value) + ? value + : throw new InvalidOperationException($"CDP response property '{string.Join(".", propertyPath)}' was not an integer."); + } + + private static string CreateBrowserCommandEnvelope(string action, string resultJson) + { + return JsonSerializer.Serialize(new + { + action, + result = JsonSerializer.Deserialize(resultJson) + }); + } + + private static ActiveBrowserSession? SelectActiveSession(ResourceSessionState resourceState) + { + return resourceState.LastSessionId is { } lastSessionId && + resourceState.ActiveSessions.TryGetValue(lastSessionId, out var lastSession) + ? lastSession + : resourceState.ActiveSessions.Count == 0 + ? null + : resourceState.ActiveSessions.Values.MaxBy(static session => session.StartedAt); + } + + private static TimeSpan CreateEvaluationTimeout(int timeoutMilliseconds) + { + return TimeSpan.FromMilliseconds(timeoutMilliseconds + 5_000); + } + private static DateTime? GetStartTimeStamp(ResourceSessionState resourceState, DateTime? fallbackStartTimeStamp) { if (resourceState.ActiveSessions.Count > 0) @@ -623,6 +1266,21 @@ private sealed record BrowserSessionPropertyValue( string? PageCdpEndpoint, string TargetId); + private sealed record BrowserLogsNavigateCommandResult( + string SessionId, + string Browser, + string TargetId, + string TargetUrl); + + private sealed record BrowserLogsCloseBrowserCommandResult( + string SessionId, + string Browser, + string BrowserExecutable, + string BrowserHostOwnership, + int? ProcessId, + string TargetId, + string TargetUrl); + private static string FormatDebugEndpoint(Uri? browserDebugEndpoint) => browserDebugEndpoint?.ToString() ?? "pipe"; diff --git a/src/Aspire.Hosting.Browsers/BrowserPageSession.cs b/src/Aspire.Hosting.Browsers/BrowserPageSession.cs index 3a493fb62cf..d2c45ab10c0 100644 --- a/src/Aspire.Hosting.Browsers/BrowserPageSession.cs +++ b/src/Aspire.Hosting.Browsers/BrowserPageSession.cs @@ -11,7 +11,7 @@ internal delegate Task BrowserLogsCdpConnectionFactor ILogger logger, CancellationToken cancellationToken); -// Owns one browser page/tab for one browser-log session. CDP calls pages "targets", but this layer intentionally +// Owns one browser page/tab for one browser automation session. CDP calls pages "targets", but this layer intentionally // models the user-visible page session. The host may be shared by many sessions, while each BrowserPageSession has // its own browser CDP connection, attached target session id, instrumentation setup, lifecycle monitoring, and // reconnection loop. @@ -74,7 +74,7 @@ private BrowserPageSession( public Task Completion => _monitorTask ?? throw new InvalidOperationException("Browser page session has not started."); - public async Task CaptureScreenshotAsync(CancellationToken cancellationToken) + public async Task CaptureScreenshotAsync(BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { await _connectionLock.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -88,7 +88,88 @@ public async Task CaptureScreenshotAsync(Can // Keep the lock for the whole CDP command. Capturing only the fields under the lock is not enough because // BrowserPageSession.DisposeAsync and reconnect both dispose the connection object itself. using var captureCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _stopCts.Token); - return await connection.CaptureScreenshotAsync(targetSessionId, captureCts.Token).ConfigureAwait(false); + return await connection.CaptureScreenshotAsync(targetSessionId, options, captureCts.Token).ConfigureAwait(false); + } + finally + { + _connectionLock.Release(); + } + } + + public async Task NavigateAsync(Uri url, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(url); + + await _connectionLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this); + + var connection = _connection ?? throw new InvalidOperationException("Tracked browser debug connection is not available."); + var targetSessionId = _targetSessionId ?? throw new InvalidOperationException("Browser target session id is not available before the target session starts."); + + using var navigateCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _stopCts.Token); + await connection.NavigateAsync(targetSessionId, url, navigateCts.Token).ConfigureAwait(false); + } + finally + { + _connectionLock.Release(); + } + } + + public async Task EvaluateJsonAsync(string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(expression); + + await _connectionLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this); + + var connection = _connection ?? throw new InvalidOperationException("Tracked browser debug connection is not available."); + var targetSessionId = _targetSessionId ?? throw new InvalidOperationException("Browser target session id is not available before the target session starts."); + + using var evaluateCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _stopCts.Token); + var result = await connection.EvaluateAsync(targetSessionId, expression, timeout, evaluateCts.Token).ConfigureAwait(false); + + return result.Result?.Value switch + { + BrowserLogsCdpProtocolStringValue stringValue => stringValue.Value, + BrowserLogsCdpProtocolNullValue => "null", + null => throw new InvalidOperationException("Tracked browser script evaluation returned no value."), + _ => throw new InvalidOperationException("Tracked browser script evaluation did not return JSON text.") + }; + } + finally + { + _connectionLock.Release(); + } + } + + public async Task SendCdpCommandJsonAsync(string method, string? parametersJson, string session, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(method); + ArgumentException.ThrowIfNullOrWhiteSpace(session); + + await _connectionLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this); + + var connection = _connection ?? throw new InvalidOperationException("Tracked browser debug connection is not available."); + var targetSessionId = _targetSessionId ?? throw new InvalidOperationException("Browser target session id is not available before the target session starts."); + var commandSessionId = session switch + { + "page" => targetSessionId, + "browser" => null, + _ => throw new InvalidOperationException("CDP command session must be 'page' or 'browser'.") + }; + + using var cdpCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _stopCts.Token); + return await connection.SendRawCommandAsync(commandSessionId, method, parametersJson, cdpCts.Token).ConfigureAwait(false); } finally { diff --git a/src/Aspire.Hosting.Browsers/BrowserScreenshotCaptureOptions.cs b/src/Aspire.Hosting.Browsers/BrowserScreenshotCaptureOptions.cs new file mode 100644 index 00000000000..b1cdaeb2dd9 --- /dev/null +++ b/src/Aspire.Hosting.Browsers/BrowserScreenshotCaptureOptions.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.Hosting; + +internal readonly record struct BrowserScreenshotCaptureOptions(string Format, int? Quality, bool FullPage) +{ + public static BrowserScreenshotCaptureOptions Default { get; } = new("png", Quality: null, FullPage: false); + + public string FileExtension => Format switch + { + "jpeg" => ".jpg", + "webp" => ".webp", + _ => ".png" + }; + + public string MimeType => Format switch + { + "jpeg" => "image/jpeg", + "webp" => "image/webp", + _ => "image/png" + }; +} diff --git a/src/Aspire.Hosting.Browsers/BrowserUserDataMode.cs b/src/Aspire.Hosting.Browsers/BrowserUserDataMode.cs index 572623d3257..ee1c405b724 100644 --- a/src/Aspire.Hosting.Browsers/BrowserUserDataMode.cs +++ b/src/Aspire.Hosting.Browsers/BrowserUserDataMode.cs @@ -8,7 +8,7 @@ namespace Aspire.Hosting; /// /// Selects the Chromium user data directory used by tracked browser sessions. /// -[Experimental("ASPIREBROWSERLOGS001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] +[Experimental("ASPIREBROWSERAUTOMATION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public enum BrowserUserDataMode { /// diff --git a/src/Aspire.Hosting.Browsers/BrowserUserDataPathResolver.cs b/src/Aspire.Hosting.Browsers/BrowserUserDataPathResolver.cs index 95c1773ea58..8cf24cb6748 100644 --- a/src/Aspire.Hosting.Browsers/BrowserUserDataPathResolver.cs +++ b/src/Aspire.Hosting.Browsers/BrowserUserDataPathResolver.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only using System.Globalization; using Aspire.Hosting.Browsers.Resources; diff --git a/src/Aspire.Hosting.Browsers/ChromiumBrowserResolver.cs b/src/Aspire.Hosting.Browsers/ChromiumBrowserResolver.cs index aca48527a38..ce8f78699ab 100644 --- a/src/Aspire.Hosting.Browsers/ChromiumBrowserResolver.cs +++ b/src/Aspire.Hosting.Browsers/ChromiumBrowserResolver.cs @@ -11,7 +11,7 @@ namespace Aspire.Hosting; /// Resolves Chromium-based browser executables, user data directories, and profile directories. /// /// -/// This type translates the resolved browser-log configuration into local machine paths. Keep OS/browser probing here +/// This type translates the resolved browser automation configuration into local machine paths. Keep OS/browser probing here /// so stays focused on configuration precedence and effective option values. /// internal static class ChromiumBrowserResolver diff --git a/src/Aspire.Hosting.Browsers/IBrowserHost.cs b/src/Aspire.Hosting.Browsers/IBrowserHost.cs index 2c9d296190f..8e975b664aa 100644 --- a/src/Aspire.Hosting.Browsers/IBrowserHost.cs +++ b/src/Aspire.Hosting.Browsers/IBrowserHost.cs @@ -36,7 +36,7 @@ Task CreateCdpConnectionAsync( ILogger logger, CancellationToken cancellationToken); - // Creates a page/tab owned by one tracked browser-log session. The returned session owns only that page target; + // Creates a page/tab owned by one tracked browser automation session. The returned session owns only that page target; // disposing it must never close the browser process. Host implementations hide CDP event fanout and recovery // so callers cannot accidentally share a page target or call Browser.close on an adopted browser. Task CreatePageSessionAsync( @@ -57,7 +57,13 @@ internal interface IBrowserPageSession : IAsyncDisposable // or the host terminated. Host-level reconnects should reattach and preserve this session when possible. Task Completion { get; } - Task CaptureScreenshotAsync(CancellationToken cancellationToken); + Task CaptureScreenshotAsync(BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken); + + Task NavigateAsync(Uri url, CancellationToken cancellationToken); + + Task EvaluateJsonAsync(string expression, TimeSpan? timeout, CancellationToken cancellationToken); + + Task SendCdpCommandJsonAsync(string method, string? parametersJson, string session, CancellationToken cancellationToken); } // Normalized page-session completion signal consumed by BrowserLogsRunningSession so manager state is independent of diff --git a/src/Aspire.Hosting.Browsers/IBrowserLogsSessionManager.cs b/src/Aspire.Hosting.Browsers/IBrowserLogsSessionManager.cs index 1e5424176d4..e59968f3b1a 100644 --- a/src/Aspire.Hosting.Browsers/IBrowserLogsSessionManager.cs +++ b/src/Aspire.Hosting.Browsers/IBrowserLogsSessionManager.cs @@ -5,9 +5,91 @@ namespace Aspire.Hosting; internal interface IBrowserLogsSessionManager { - Task StartSessionAsync(BrowserLogsResource resource, BrowserConfiguration configuration, string resourceName, Uri url, CancellationToken cancellationToken); + Task StartSessionAsync(BrowserAutomationResource resource, BrowserConfiguration configuration, string resourceName, Uri url, CancellationToken cancellationToken); - Task CaptureScreenshotAsync(string resourceName, CancellationToken cancellationToken); + Task CaptureScreenshotAsync(string resourceName, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken); + + Task GetPageSnapshotAsync(string resourceName, int maxElements, int maxTextLength, CancellationToken cancellationToken); + + Task GetAsync(string resourceName, string property, string? selector, string? name, CancellationToken cancellationToken); + + Task IsAsync(string resourceName, string state, string selector, CancellationToken cancellationToken); + + Task FindAsync(string resourceName, string kind, string value, string? name, int index, CancellationToken cancellationToken); + + Task HighlightAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task EvaluateAsync(string resourceName, string expression, CancellationToken cancellationToken); + + Task CookiesAsync(string resourceName, string action, string? name, string? value, string? domain, string? path, CancellationToken cancellationToken); + + Task StorageAsync(string resourceName, string area, string action, string? key, string? value, CancellationToken cancellationToken); + + Task StateAsync(string resourceName, string action, string? state, bool clearExisting, CancellationToken cancellationToken); + + Task CdpAsync(string resourceName, string method, string? parametersJson, string session, CancellationToken cancellationToken); + + Task TabsAsync(string resourceName, string action, string? url, string? targetId, CancellationToken cancellationToken); + + Task FramesAsync(string resourceName, CancellationToken cancellationToken); + + Task DialogAsync(string resourceName, string action, string? promptText, CancellationToken cancellationToken); + + Task DownloadsAsync(string resourceName, string behavior, string? downloadPath, bool eventsEnabled, CancellationToken cancellationToken); + + Task UploadAsync(string resourceName, string selector, string files, CancellationToken cancellationToken); + + Task GetUrlAsync(string resourceName, CancellationToken cancellationToken); + + Task GoBackAsync(string resourceName, CancellationToken cancellationToken); + + Task GoForwardAsync(string resourceName, CancellationToken cancellationToken); + + Task ReloadAsync(string resourceName, CancellationToken cancellationToken); + + Task NavigateAsync(BrowserAutomationResource resource, string resourceName, Uri url, CancellationToken cancellationToken); + + Task ClickAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task DoubleClickAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task FillAsync(string resourceName, string selector, string value, CancellationToken cancellationToken); + + Task CheckAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task UncheckAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task FocusAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task TypeAsync(string resourceName, string selector, string text, CancellationToken cancellationToken); + + Task PressAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken); + + Task HoverAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task SelectAsync(string resourceName, string selector, string value, CancellationToken cancellationToken); + + Task ScrollAsync(string resourceName, string? selector, int deltaX, int deltaY, CancellationToken cancellationToken); + + Task ScrollIntoViewAsync(string resourceName, string selector, CancellationToken cancellationToken); + + Task KeyDownAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken); + + Task KeyUpAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken); + + Task MouseAsync(string resourceName, string action, int x, int y, string? button, int deltaX, int deltaY, CancellationToken cancellationToken); + + Task WaitForAsync(string resourceName, string? selector, string? text, int timeoutMilliseconds, CancellationToken cancellationToken); + + Task WaitForUrlAsync(string resourceName, string url, string match, int timeoutMilliseconds, CancellationToken cancellationToken); + + Task WaitForLoadStateAsync(string resourceName, string state, int timeoutMilliseconds, CancellationToken cancellationToken); + + Task WaitForElementStateAsync(string resourceName, string selector, string state, int timeoutMilliseconds, CancellationToken cancellationToken); + + Task WaitForFunctionAsync(string resourceName, string function, int timeoutMilliseconds, CancellationToken cancellationToken); + + Task CloseActiveSessionAsync(string resourceName, CancellationToken cancellationToken); } internal sealed record BrowserLogsScreenshotCaptureResult( diff --git a/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.Designer.cs b/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.Designer.cs index 4069395ef78..996c558b8ea 100644 --- a/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.Designer.cs +++ b/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.Designer.cs @@ -47,6 +47,234 @@ internal static class BrowserCommandStrings internal static string ConfigureTrackedBrowserSaveFailed => GetString(nameof(ConfigureTrackedBrowserSaveFailed)); internal static string CaptureScreenshotDescription => GetString(nameof(CaptureScreenshotDescription)); internal static string CaptureScreenshotName => GetString(nameof(CaptureScreenshotName)); + internal static string InspectBrowserDescription => GetString(nameof(InspectBrowserDescription)); + internal static string InspectBrowserName => GetString(nameof(InspectBrowserName)); + internal static string InspectBrowserSucceeded => GetString(nameof(InspectBrowserSucceeded)); + internal static string GetBrowserDescription => GetString(nameof(GetBrowserDescription)); + internal static string GetBrowserName => GetString(nameof(GetBrowserName)); + internal static string GetBrowserSucceeded => GetString(nameof(GetBrowserSucceeded)); + internal static string IsBrowserDescription => GetString(nameof(IsBrowserDescription)); + internal static string IsBrowserName => GetString(nameof(IsBrowserName)); + internal static string IsBrowserSucceeded => GetString(nameof(IsBrowserSucceeded)); + internal static string FindBrowserDescription => GetString(nameof(FindBrowserDescription)); + internal static string FindBrowserName => GetString(nameof(FindBrowserName)); + internal static string FindBrowserSucceeded => GetString(nameof(FindBrowserSucceeded)); + internal static string HighlightBrowserDescription => GetString(nameof(HighlightBrowserDescription)); + internal static string HighlightBrowserName => GetString(nameof(HighlightBrowserName)); + internal static string HighlightBrowserSucceeded => GetString(nameof(HighlightBrowserSucceeded)); + internal static string EvaluateBrowserDescription => GetString(nameof(EvaluateBrowserDescription)); + internal static string EvaluateBrowserName => GetString(nameof(EvaluateBrowserName)); + internal static string EvaluateBrowserSucceeded => GetString(nameof(EvaluateBrowserSucceeded)); + internal static string CookiesBrowserDescription => GetString(nameof(CookiesBrowserDescription)); + internal static string CookiesBrowserName => GetString(nameof(CookiesBrowserName)); + internal static string CookiesBrowserSucceeded => GetString(nameof(CookiesBrowserSucceeded)); + internal static string StorageBrowserDescription => GetString(nameof(StorageBrowserDescription)); + internal static string StorageBrowserName => GetString(nameof(StorageBrowserName)); + internal static string StorageBrowserSucceeded => GetString(nameof(StorageBrowserSucceeded)); + internal static string StateBrowserDescription => GetString(nameof(StateBrowserDescription)); + internal static string StateBrowserName => GetString(nameof(StateBrowserName)); + internal static string StateBrowserSucceeded => GetString(nameof(StateBrowserSucceeded)); + internal static string CdpBrowserDescription => GetString(nameof(CdpBrowserDescription)); + internal static string CdpBrowserName => GetString(nameof(CdpBrowserName)); + internal static string CdpBrowserSucceeded => GetString(nameof(CdpBrowserSucceeded)); + internal static string TabsBrowserDescription => GetString(nameof(TabsBrowserDescription)); + internal static string TabsBrowserName => GetString(nameof(TabsBrowserName)); + internal static string TabsBrowserSucceeded => GetString(nameof(TabsBrowserSucceeded)); + internal static string FramesBrowserDescription => GetString(nameof(FramesBrowserDescription)); + internal static string FramesBrowserName => GetString(nameof(FramesBrowserName)); + internal static string FramesBrowserSucceeded => GetString(nameof(FramesBrowserSucceeded)); + internal static string DialogBrowserDescription => GetString(nameof(DialogBrowserDescription)); + internal static string DialogBrowserName => GetString(nameof(DialogBrowserName)); + internal static string DialogBrowserSucceeded => GetString(nameof(DialogBrowserSucceeded)); + internal static string DownloadsBrowserDescription => GetString(nameof(DownloadsBrowserDescription)); + internal static string DownloadsBrowserName => GetString(nameof(DownloadsBrowserName)); + internal static string DownloadsBrowserSucceeded => GetString(nameof(DownloadsBrowserSucceeded)); + internal static string UploadBrowserDescription => GetString(nameof(UploadBrowserDescription)); + internal static string UploadBrowserName => GetString(nameof(UploadBrowserName)); + internal static string UploadBrowserSucceeded => GetString(nameof(UploadBrowserSucceeded)); + internal static string BrowserUrlDescription => GetString(nameof(BrowserUrlDescription)); + internal static string BrowserUrlName => GetString(nameof(BrowserUrlName)); + internal static string BrowserUrlSucceeded => GetString(nameof(BrowserUrlSucceeded)); + internal static string BackBrowserDescription => GetString(nameof(BackBrowserDescription)); + internal static string BackBrowserName => GetString(nameof(BackBrowserName)); + internal static string BackBrowserSucceeded => GetString(nameof(BackBrowserSucceeded)); + internal static string ForwardBrowserDescription => GetString(nameof(ForwardBrowserDescription)); + internal static string ForwardBrowserName => GetString(nameof(ForwardBrowserName)); + internal static string ForwardBrowserSucceeded => GetString(nameof(ForwardBrowserSucceeded)); + internal static string ReloadBrowserDescription => GetString(nameof(ReloadBrowserDescription)); + internal static string ReloadBrowserName => GetString(nameof(ReloadBrowserName)); + internal static string ReloadBrowserSucceeded => GetString(nameof(ReloadBrowserSucceeded)); + internal static string NavigateBrowserDescription => GetString(nameof(NavigateBrowserDescription)); + internal static string NavigateBrowserName => GetString(nameof(NavigateBrowserName)); + internal static string NavigateBrowserSucceeded => GetString(nameof(NavigateBrowserSucceeded)); + internal static string ClickBrowserDescription => GetString(nameof(ClickBrowserDescription)); + internal static string ClickBrowserName => GetString(nameof(ClickBrowserName)); + internal static string ClickBrowserSucceeded => GetString(nameof(ClickBrowserSucceeded)); + internal static string DoubleClickBrowserDescription => GetString(nameof(DoubleClickBrowserDescription)); + internal static string DoubleClickBrowserName => GetString(nameof(DoubleClickBrowserName)); + internal static string DoubleClickBrowserSucceeded => GetString(nameof(DoubleClickBrowserSucceeded)); + internal static string FillBrowserDescription => GetString(nameof(FillBrowserDescription)); + internal static string FillBrowserName => GetString(nameof(FillBrowserName)); + internal static string FillBrowserSucceeded => GetString(nameof(FillBrowserSucceeded)); + internal static string CheckBrowserDescription => GetString(nameof(CheckBrowserDescription)); + internal static string CheckBrowserName => GetString(nameof(CheckBrowserName)); + internal static string CheckBrowserSucceeded => GetString(nameof(CheckBrowserSucceeded)); + internal static string UncheckBrowserDescription => GetString(nameof(UncheckBrowserDescription)); + internal static string UncheckBrowserName => GetString(nameof(UncheckBrowserName)); + internal static string UncheckBrowserSucceeded => GetString(nameof(UncheckBrowserSucceeded)); + internal static string FocusBrowserElementDescription => GetString(nameof(FocusBrowserElementDescription)); + internal static string FocusBrowserElementName => GetString(nameof(FocusBrowserElementName)); + internal static string FocusBrowserElementSucceeded => GetString(nameof(FocusBrowserElementSucceeded)); + internal static string TypeBrowserTextDescription => GetString(nameof(TypeBrowserTextDescription)); + internal static string TypeBrowserTextName => GetString(nameof(TypeBrowserTextName)); + internal static string TypeBrowserTextSucceeded => GetString(nameof(TypeBrowserTextSucceeded)); + internal static string PressBrowserKeyDescription => GetString(nameof(PressBrowserKeyDescription)); + internal static string PressBrowserKeyName => GetString(nameof(PressBrowserKeyName)); + internal static string PressBrowserKeySucceeded => GetString(nameof(PressBrowserKeySucceeded)); + internal static string KeyDownBrowserDescription => GetString(nameof(KeyDownBrowserDescription)); + internal static string KeyDownBrowserName => GetString(nameof(KeyDownBrowserName)); + internal static string KeyDownBrowserSucceeded => GetString(nameof(KeyDownBrowserSucceeded)); + internal static string KeyUpBrowserDescription => GetString(nameof(KeyUpBrowserDescription)); + internal static string KeyUpBrowserName => GetString(nameof(KeyUpBrowserName)); + internal static string KeyUpBrowserSucceeded => GetString(nameof(KeyUpBrowserSucceeded)); + internal static string HoverBrowserElementDescription => GetString(nameof(HoverBrowserElementDescription)); + internal static string HoverBrowserElementName => GetString(nameof(HoverBrowserElementName)); + internal static string HoverBrowserElementSucceeded => GetString(nameof(HoverBrowserElementSucceeded)); + internal static string SelectBrowserOptionDescription => GetString(nameof(SelectBrowserOptionDescription)); + internal static string SelectBrowserOptionName => GetString(nameof(SelectBrowserOptionName)); + internal static string SelectBrowserOptionSucceeded => GetString(nameof(SelectBrowserOptionSucceeded)); + internal static string ScrollBrowserDescription => GetString(nameof(ScrollBrowserDescription)); + internal static string ScrollBrowserName => GetString(nameof(ScrollBrowserName)); + internal static string ScrollBrowserSucceeded => GetString(nameof(ScrollBrowserSucceeded)); + internal static string ScrollIntoViewBrowserDescription => GetString(nameof(ScrollIntoViewBrowserDescription)); + internal static string ScrollIntoViewBrowserName => GetString(nameof(ScrollIntoViewBrowserName)); + internal static string ScrollIntoViewBrowserSucceeded => GetString(nameof(ScrollIntoViewBrowserSucceeded)); + internal static string MouseBrowserDescription => GetString(nameof(MouseBrowserDescription)); + internal static string MouseBrowserName => GetString(nameof(MouseBrowserName)); + internal static string MouseBrowserSucceeded => GetString(nameof(MouseBrowserSucceeded)); + internal static string WaitForBrowserDescription => GetString(nameof(WaitForBrowserDescription)); + internal static string WaitForBrowserName => GetString(nameof(WaitForBrowserName)); + internal static string WaitForBrowserSucceeded => GetString(nameof(WaitForBrowserSucceeded)); + internal static string WaitBrowserDescription => GetString(nameof(WaitBrowserDescription)); + internal static string WaitBrowserName => GetString(nameof(WaitBrowserName)); + internal static string WaitBrowserSucceeded => GetString(nameof(WaitBrowserSucceeded)); + internal static string WaitForBrowserUrlDescription => GetString(nameof(WaitForBrowserUrlDescription)); + internal static string WaitForBrowserUrlName => GetString(nameof(WaitForBrowserUrlName)); + internal static string WaitForBrowserUrlSucceeded => GetString(nameof(WaitForBrowserUrlSucceeded)); + internal static string WaitForBrowserLoadStateDescription => GetString(nameof(WaitForBrowserLoadStateDescription)); + internal static string WaitForBrowserLoadStateName => GetString(nameof(WaitForBrowserLoadStateName)); + internal static string WaitForBrowserLoadStateSucceeded => GetString(nameof(WaitForBrowserLoadStateSucceeded)); + internal static string WaitForBrowserElementStateDescription => GetString(nameof(WaitForBrowserElementStateDescription)); + internal static string WaitForBrowserElementStateName => GetString(nameof(WaitForBrowserElementStateName)); + internal static string WaitForBrowserElementStateSucceeded => GetString(nameof(WaitForBrowserElementStateSucceeded)); + internal static string CloseTrackedBrowserDescription => GetString(nameof(CloseTrackedBrowserDescription)); + internal static string CloseTrackedBrowserName => GetString(nameof(CloseTrackedBrowserName)); + internal static string CloseTrackedBrowserSucceeded => GetString(nameof(CloseTrackedBrowserSucceeded)); + internal static string SelectorArgumentLabel => GetString(nameof(SelectorArgumentLabel)); + internal static string SelectorArgumentDescription => GetString(nameof(SelectorArgumentDescription)); + internal static string SnapshotAfterArgumentLabel => GetString(nameof(SnapshotAfterArgumentLabel)); + internal static string SnapshotAfterArgumentDescription => GetString(nameof(SnapshotAfterArgumentDescription)); + internal static string ValueArgumentLabel => GetString(nameof(ValueArgumentLabel)); + internal static string ValueArgumentDescription => GetString(nameof(ValueArgumentDescription)); + internal static string PropertyArgumentLabel => GetString(nameof(PropertyArgumentLabel)); + internal static string PropertyArgumentDescription => GetString(nameof(PropertyArgumentDescription)); + internal static string NameArgumentLabel => GetString(nameof(NameArgumentLabel)); + internal static string NameArgumentDescription => GetString(nameof(NameArgumentDescription)); + internal static string KindArgumentLabel => GetString(nameof(KindArgumentLabel)); + internal static string KindArgumentDescription => GetString(nameof(KindArgumentDescription)); + internal static string FindValueArgumentLabel => GetString(nameof(FindValueArgumentLabel)); + internal static string FindValueArgumentDescription => GetString(nameof(FindValueArgumentDescription)); + internal static string IndexArgumentLabel => GetString(nameof(IndexArgumentLabel)); + internal static string IndexArgumentDescription => GetString(nameof(IndexArgumentDescription)); + internal static string ExpressionArgumentLabel => GetString(nameof(ExpressionArgumentLabel)); + internal static string ExpressionArgumentDescription => GetString(nameof(ExpressionArgumentDescription)); + internal static string ActionArgumentLabel => GetString(nameof(ActionArgumentLabel)); + internal static string CookiesActionArgumentDescription => GetString(nameof(CookiesActionArgumentDescription)); + internal static string CookieNameArgumentLabel => GetString(nameof(CookieNameArgumentLabel)); + internal static string CookieNameArgumentDescription => GetString(nameof(CookieNameArgumentDescription)); + internal static string CookieValueArgumentLabel => GetString(nameof(CookieValueArgumentLabel)); + internal static string CookieValueArgumentDescription => GetString(nameof(CookieValueArgumentDescription)); + internal static string CookieDomainArgumentLabel => GetString(nameof(CookieDomainArgumentLabel)); + internal static string CookieDomainArgumentDescription => GetString(nameof(CookieDomainArgumentDescription)); + internal static string CookiePathArgumentLabel => GetString(nameof(CookiePathArgumentLabel)); + internal static string CookiePathArgumentDescription => GetString(nameof(CookiePathArgumentDescription)); + internal static string StorageAreaArgumentLabel => GetString(nameof(StorageAreaArgumentLabel)); + internal static string StorageAreaArgumentDescription => GetString(nameof(StorageAreaArgumentDescription)); + internal static string StorageActionArgumentDescription => GetString(nameof(StorageActionArgumentDescription)); + internal static string StorageKeyArgumentLabel => GetString(nameof(StorageKeyArgumentLabel)); + internal static string StorageKeyArgumentDescription => GetString(nameof(StorageKeyArgumentDescription)); + internal static string StorageValueArgumentLabel => GetString(nameof(StorageValueArgumentLabel)); + internal static string StorageValueArgumentDescription => GetString(nameof(StorageValueArgumentDescription)); + internal static string StateActionArgumentDescription => GetString(nameof(StateActionArgumentDescription)); + internal static string StateJsonArgumentLabel => GetString(nameof(StateJsonArgumentLabel)); + internal static string StateJsonArgumentDescription => GetString(nameof(StateJsonArgumentDescription)); + internal static string ClearExistingArgumentLabel => GetString(nameof(ClearExistingArgumentLabel)); + internal static string ClearExistingArgumentDescription => GetString(nameof(ClearExistingArgumentDescription)); + internal static string CdpMethodArgumentLabel => GetString(nameof(CdpMethodArgumentLabel)); + internal static string CdpMethodArgumentDescription => GetString(nameof(CdpMethodArgumentDescription)); + internal static string CdpParamsArgumentLabel => GetString(nameof(CdpParamsArgumentLabel)); + internal static string CdpParamsArgumentDescription => GetString(nameof(CdpParamsArgumentDescription)); + internal static string CdpSessionArgumentLabel => GetString(nameof(CdpSessionArgumentLabel)); + internal static string CdpSessionArgumentDescription => GetString(nameof(CdpSessionArgumentDescription)); + internal static string TabsActionArgumentDescription => GetString(nameof(TabsActionArgumentDescription)); + internal static string TabUrlArgumentDescription => GetString(nameof(TabUrlArgumentDescription)); + internal static string TargetIdArgumentLabel => GetString(nameof(TargetIdArgumentLabel)); + internal static string TargetIdArgumentDescription => GetString(nameof(TargetIdArgumentDescription)); + internal static string DialogActionArgumentDescription => GetString(nameof(DialogActionArgumentDescription)); + internal static string PromptTextArgumentLabel => GetString(nameof(PromptTextArgumentLabel)); + internal static string PromptTextArgumentDescription => GetString(nameof(PromptTextArgumentDescription)); + internal static string DownloadBehaviorArgumentLabel => GetString(nameof(DownloadBehaviorArgumentLabel)); + internal static string DownloadBehaviorArgumentDescription => GetString(nameof(DownloadBehaviorArgumentDescription)); + internal static string DownloadPathArgumentLabel => GetString(nameof(DownloadPathArgumentLabel)); + internal static string DownloadPathArgumentDescription => GetString(nameof(DownloadPathArgumentDescription)); + internal static string DownloadEventsEnabledArgumentLabel => GetString(nameof(DownloadEventsEnabledArgumentLabel)); + internal static string DownloadEventsEnabledArgumentDescription => GetString(nameof(DownloadEventsEnabledArgumentDescription)); + internal static string FilesArgumentLabel => GetString(nameof(FilesArgumentLabel)); + internal static string FilesArgumentDescription => GetString(nameof(FilesArgumentDescription)); + internal static string ScreenshotFormatArgumentLabel => GetString(nameof(ScreenshotFormatArgumentLabel)); + internal static string ScreenshotFormatArgumentDescription => GetString(nameof(ScreenshotFormatArgumentDescription)); + internal static string ScreenshotQualityArgumentLabel => GetString(nameof(ScreenshotQualityArgumentLabel)); + internal static string ScreenshotQualityArgumentDescription => GetString(nameof(ScreenshotQualityArgumentDescription)); + internal static string FullPageArgumentLabel => GetString(nameof(FullPageArgumentLabel)); + internal static string FullPageArgumentDescription => GetString(nameof(FullPageArgumentDescription)); + internal static string TypeTextArgumentDescription => GetString(nameof(TypeTextArgumentDescription)); + internal static string SelectValueArgumentDescription => GetString(nameof(SelectValueArgumentDescription)); + internal static string KeyArgumentLabel => GetString(nameof(KeyArgumentLabel)); + internal static string KeyArgumentDescription => GetString(nameof(KeyArgumentDescription)); + internal static string TextArgumentLabel => GetString(nameof(TextArgumentLabel)); + internal static string TextArgumentDescription => GetString(nameof(TextArgumentDescription)); + internal static string UrlContainsArgumentLabel => GetString(nameof(UrlContainsArgumentLabel)); + internal static string UrlContainsArgumentDescription => GetString(nameof(UrlContainsArgumentDescription)); + internal static string DeltaYArgumentLabel => GetString(nameof(DeltaYArgumentLabel)); + internal static string DeltaYArgumentDescription => GetString(nameof(DeltaYArgumentDescription)); + internal static string DeltaXArgumentLabel => GetString(nameof(DeltaXArgumentLabel)); + internal static string DeltaXArgumentDescription => GetString(nameof(DeltaXArgumentDescription)); + internal static string MouseActionArgumentDescription => GetString(nameof(MouseActionArgumentDescription)); + internal static string XCoordinateArgumentLabel => GetString(nameof(XCoordinateArgumentLabel)); + internal static string XCoordinateArgumentDescription => GetString(nameof(XCoordinateArgumentDescription)); + internal static string YCoordinateArgumentLabel => GetString(nameof(YCoordinateArgumentLabel)); + internal static string YCoordinateArgumentDescription => GetString(nameof(YCoordinateArgumentDescription)); + internal static string MouseButtonArgumentLabel => GetString(nameof(MouseButtonArgumentLabel)); + internal static string MouseButtonArgumentDescription => GetString(nameof(MouseButtonArgumentDescription)); + internal static string UrlArgumentLabel => GetString(nameof(UrlArgumentLabel)); + internal static string UrlArgumentDescription => GetString(nameof(UrlArgumentDescription)); + internal static string WaitUrlArgumentDescription => GetString(nameof(WaitUrlArgumentDescription)); + internal static string MatchArgumentLabel => GetString(nameof(MatchArgumentLabel)); + internal static string MatchArgumentDescription => GetString(nameof(MatchArgumentDescription)); + internal static string StateArgumentLabel => GetString(nameof(StateArgumentLabel)); + internal static string StateArgumentDescription => GetString(nameof(StateArgumentDescription)); + internal static string LoadStateArgumentLabel => GetString(nameof(LoadStateArgumentLabel)); + internal static string LoadStateArgumentDescription => GetString(nameof(LoadStateArgumentDescription)); + internal static string ElementStateArgumentLabel => GetString(nameof(ElementStateArgumentLabel)); + internal static string ElementStateArgumentDescription => GetString(nameof(ElementStateArgumentDescription)); + internal static string FunctionArgumentLabel => GetString(nameof(FunctionArgumentLabel)); + internal static string FunctionArgumentDescription => GetString(nameof(FunctionArgumentDescription)); + internal static string TimeoutMillisecondsArgumentLabel => GetString(nameof(TimeoutMillisecondsArgumentLabel)); + internal static string TimeoutMillisecondsArgumentDescription => GetString(nameof(TimeoutMillisecondsArgumentDescription)); + internal static string MaxElementsArgumentLabel => GetString(nameof(MaxElementsArgumentLabel)); + internal static string MaxElementsArgumentDescription => GetString(nameof(MaxElementsArgumentDescription)); + internal static string MaxTextLengthArgumentLabel => GetString(nameof(MaxTextLengthArgumentLabel)); + internal static string MaxTextLengthArgumentDescription => GetString(nameof(MaxTextLengthArgumentDescription)); private static string GetString(string name) => s_resourceManager.GetString(name, Culture)!; } diff --git a/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.resx b/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.resx index 754970e83eb..13235737cca 100644 --- a/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.resx +++ b/src/Aspire.Hosting.Browsers/Resources/BrowserCommandStrings.resx @@ -13,7 +13,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. Open tracked browser @@ -25,7 +25,7 @@ Configure tracked browser - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. Apply @@ -38,10 +38,10 @@ {0} is the Aspire resource name - All BrowserLogs resources + All Browser Automation resources - all BrowserLogs resources + all Browser Automation resources Browser @@ -116,4 +116,688 @@ Capture screenshot + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + Inspect browser page + + + Captured browser page snapshot. + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + Get browser value + + + Retrieved browser value. + + + Check whether a selected browser element is visible, enabled, or checked. + + + Check browser element state + + + Checked browser element state. + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + Find browser element + + + Found browser element. + + + Highlight an element in the active tracked browser page for visual debugging. + + + Highlight browser element + + + Highlighted browser element. + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + Evaluate browser script + + + Evaluated browser script. + + + Get, set, or clear cookies visible to the active tracked browser page. + + + Manage browser cookies + + + Updated browser cookies. + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + Manage browser storage + + + Updated browser storage. + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + Manage browser state + + + Updated browser state. + + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + Send CDP command + + + Sent CDP command. + + + List, open, or close browser page targets in the tracked browser. + + + Manage browser tabs + + + Managed browser tabs. + + + List frame elements in the active tracked browser page. + + + List browser frames + + + Listed browser frames. + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + Handle browser dialog + + + Handled browser dialog. + + + Configure download behavior for the tracked browser. + + + Configure browser downloads + + + Configured browser downloads. + + + Attach one or more local files to a file input in the active tracked browser page. + + + Upload browser files + + + Uploaded browser files. + + + Return the current URL, title, and ready state for the active tracked browser page. + + + Get browser URL + + + Returned browser URL. + + + Navigate the active tracked browser page back in its history. + + + Go back + + + Navigated browser back. + + + Navigate the active tracked browser page forward in its history. + + + Go forward + + + Navigated browser forward. + + + Reload the active tracked browser page. + + + Reload browser + + + Reloaded browser. + + + Navigate the active tracked browser page to an absolute URL. + + + Navigate browser + + + Navigated browser page. + + + Click an element in the active tracked browser page using a CSS selector. + + + Click browser element + + + Clicked browser element. + + + Double-click an element in the active tracked browser page using a CSS selector. + + + Double-click browser element + + + Double-clicked browser element. + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + Fill browser field + + + Filled browser field. + + + Check a checkbox or other checkable element in the active tracked browser page. + + + Check browser element + + + Checked browser element. + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + Uncheck browser element + + + Unchecked browser element. + + + Focus an element in the active tracked browser page using a CSS selector. + + + Focus browser element + + + Focused browser element. + + + Type text into an input, textarea, or editable element in the active tracked browser page. + + + Type browser text + + + Typed browser text. + + + Press a key on a selected or currently focused element in the active tracked browser page. + + + Press browser key + + + Pressed browser key. + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + Key down in browser + + + Dispatched browser keydown. + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + Key up in browser + + + Dispatched browser keyup. + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + Hover browser element + + + Hovered browser element. + + + Select an option in a browser select element by option value or visible text. + + + Select browser option + + + Selected browser option. + + + Scroll the active tracked browser page or a selected scrollable element. + + + Scroll browser + + + Scrolled browser. + + + Scroll an element in the active tracked browser page into view. + + + Scroll element into view + + + Scrolled browser element into view. + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + Dispatch browser mouse input + + + Dispatched browser mouse input. + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + Wait for browser page + + + Browser wait condition was met. + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + Wait + + + Browser wait condition was met. + + + Wait until the active tracked browser page URL matches the expected value. + + + Wait for browser URL + + + Browser URL wait condition was met. + + + Wait until the active tracked browser page reaches a load state. + + + Wait for browser load state + + + Browser load state wait condition was met. + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + Wait for browser element state + + + Browser element state wait condition was met. + + + Close the active tracked browser page for this resource. + + + Close tracked browser + + + Closed tracked browser page. + + + Selector + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + Snapshot after + + + Return a fresh browser page snapshot with the action result for immediate verification. + + + Value + + + Text value to assign to the target field. + + + Property + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + Name + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + Kind + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + Find value + + + Search text, role, test ID, or CSS selector depending on the find strategy. + + + Index + + + One-based element index used by nth find strategy. + + + Expression + + + JavaScript expression or zero-argument function to evaluate in the browser page. + + + Action + + + Cookie action to perform: get, set, or clear. + + + Cookie name + + + Optional cookie name. Required when setting a cookie. + + + Cookie value + + + Cookie value to set. Empty text clears the cookie value. + + + Cookie domain + + + Optional cookie domain used when setting or clearing a cookie. + + + Cookie path + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + Storage area + + + Browser storage area: local or session. + + + Storage action to perform: get, set, or clear. + + + Storage key + + + Optional storage key. Required when setting a storage value. + + + Storage value + + + Storage value to set. Empty text stores an empty string. + + + Browser state action to perform: get, set, or clear. + + + State JSON + + + Browser state JSON returned by the get action. Required when setting browser state. + + + Clear existing + + + Clear existing cookies and storage before restoring state. + + + CDP method + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + CDP parameters + + + Optional JSON object for the CDP command parameters. + + + CDP session + + + Send the command to the attached page session or the browser-level connection. + + + Tab action to perform: list, open, or close. + + + Absolute URL for a new tab. Required when opening a tab. + + + Target ID + + + Browser target ID. Required when closing a tab. + + + Dialog action to perform: accept or dismiss. + + + Prompt text + + + Optional text to enter before accepting a prompt dialog. + + + Download behavior + + + Browser download behavior: allow, allowAndName, deny, or default. + + + Download path + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + Download events enabled + + + Enable browser download progress events when supported by the browser. + + + Files + + + File path to upload, or a JSON array of file paths for multiple files. + + + Screenshot format + + + Image format for the screenshot: png, jpeg, or webp. + + + Screenshot quality + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + Full page + + + Capture beyond the current viewport when supported by the browser. + + + Text to type into the target element. + + + Option value or visible option text to select. + + + Key + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + Text + + + Visible page text to wait for. + + + URL contains + + + URL fragment to wait for in the active browser page. + + + Delta Y + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + Delta X + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + Mouse action to dispatch: move, down, up, click, or wheel. + + + X coordinate + + + Viewport X coordinate in CSS pixels. + + + Y coordinate + + + Viewport Y coordinate in CSS pixels. + + + Mouse button + + + Mouse button for down, up, and click actions. + + + URL + + + Absolute URL to navigate to. + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + Match + + + How the current browser URL should be compared with the URL argument. + + + State + + + Element state to check: visible, enabled, or checked. + + + Load state + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + Element state + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + Function + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + Timeout (milliseconds) + + + Maximum time to wait before the browser command fails. + + + Maximum elements + + + Maximum number of actionable elements to include in the page snapshot. + + + Maximum text length + + + Maximum number of visible page text characters to include in the page snapshot. + diff --git a/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.Designer.cs b/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.Designer.cs index 05bb69f6e7e..7bdfcccbb1d 100644 --- a/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.Designer.cs +++ b/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.Designer.cs @@ -27,7 +27,7 @@ internal static class BrowserMessageStrings internal static string BrowserLogsInvalidProfileMetadata => GetString(nameof(BrowserLogsInvalidProfileMetadata)); internal static string BrowserLogsProfileNotFound => GetString(nameof(BrowserLogsProfileNotFound)); internal static string BrowserLogsAmbiguousProfile => GetString(nameof(BrowserLogsAmbiguousProfile)); - internal static string BrowserLogsResourceMissingHttpEndpoint => GetString(nameof(BrowserLogsResourceMissingHttpEndpoint)); + internal static string BrowserAutomationResourceMissingHttpEndpoint => GetString(nameof(BrowserAutomationResourceMissingHttpEndpoint)); internal static string BrowserLogsEndpointNotAllocated => GetString(nameof(BrowserLogsEndpointNotAllocated)); private static string GetString(string name) => s_resourceManager.GetString(name, Culture)!; diff --git a/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.resx b/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.resx index f96360b0569..be7eb080548 100644 --- a/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.resx +++ b/src/Aspire.Hosting.Browsers/Resources/BrowserMessageStrings.resx @@ -61,8 +61,8 @@ Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. {0} is the requested browser profile, {1} is the browser user data directory path - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. {0} is the Aspire resource name diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.cs.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.cs.xlf index bb3692c0d28..43cb39e56a3 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.cs.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.cs.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.de.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.de.xlf index cc3dd21d638..b1c2afc5686 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.de.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.de.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.es.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.es.xlf index b99882a3824..0e73429d74b 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.es.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.es.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.fr.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.fr.xlf index d02e9e0a303..c7ebb9efcc5 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.fr.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.fr.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.it.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.it.xlf index 6fc6eab1c4d..09c0fa4bf8f 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.it.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.it.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ja.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ja.xlf index bff29b5c664..7ace9951096 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ja.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ja.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ko.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ko.xlf index c313bf106bf..ecaeeebfe94 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ko.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ko.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pl.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pl.xlf index f44de45f68e..6a417294239 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pl.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pl.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pt-BR.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pt-BR.xlf index ab64dcbb1af..f9df06c7552 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pt-BR.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.pt-BR.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ru.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ru.xlf index 51fa5759f00..4dd0f8989c1 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ru.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.ru.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.tr.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.tr.xlf index d6f1920df75..c21fb5e58fe 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.tr.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.tr.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hans.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hans.xlf index a428c5c6cdc..a8be1239e53 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hans.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hans.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hant.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hant.xlf index 95b1a458173..0a03b86488a 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hant.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserCommandStrings.zh-Hant.xlf @@ -2,6 +2,41 @@ + + Action + Action + + + + Navigate the active tracked browser page back in its history. + Navigate the active tracked browser page back in its history. + + + + Go back + Go back + + + + Navigated browser back. + Navigated browser back. + + + + Return the current URL, title, and ready state for the active tracked browser page. + Return the current URL, title, and ready state for the active tracked browser page. + + + + Get browser URL + Get browser URL + + + + Returned browser URL. + Returned browser URL. + + Capture a screenshot from the active tracked browser session and save it as a PNG artifact. Capture a screenshot from the active tracked browser session and save it as a PNG artifact. @@ -12,6 +47,106 @@ Capture screenshot + + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + Send a raw Chrome DevTools Protocol command to the active tracked browser page or browser connection. + + + + Send CDP command + Send CDP command + + + + Sent CDP command. + Sent CDP command. + + + + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + Chrome DevTools Protocol method name, such as Runtime.evaluate or Target.getTargets. + + + + CDP method + CDP method + + + + Optional JSON object for the CDP command parameters. + Optional JSON object for the CDP command parameters. + + + + CDP parameters + CDP parameters + + + + Send the command to the attached page session or the browser-level connection. + Send the command to the attached page session or the browser-level connection. + + + + CDP session + CDP session + + + + Check a checkbox or other checkable element in the active tracked browser page. + Check a checkbox or other checkable element in the active tracked browser page. + + + + Check browser element + Check browser element + + + + Checked browser element. + Checked browser element. + + + + Clear existing cookies and storage before restoring state. + Clear existing cookies and storage before restoring state. + + + + Clear existing + Clear existing + + + + Click an element in the active tracked browser page using a CSS selector. + Click an element in the active tracked browser page using a CSS selector. + + + + Click browser element + Click browser element + + + + Clicked browser element. + Clicked browser element. + + + + Close the active tracked browser page for this resource. + Close the active tracked browser page for this resource. + + + + Close tracked browser + Close tracked browser + + + + Closed tracked browser page. + Closed tracked browser page. + + Applied tracked browser settings for {0}. Applied tracked browser settings for {0}. @@ -58,13 +193,13 @@ - All BrowserLogs resources - All BrowserLogs resources + All Browser Automation resources + All Browser Automation resources - all BrowserLogs resources - all BrowserLogs resources + all Browser Automation resources + all Browser Automation resources @@ -98,8 +233,8 @@ - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. - Choose tracked browser settings. Resource-specific settings override global BrowserLogs settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. + Choose tracked browser settings. Resource-specific settings override global Browser Automation settings. @@ -157,9 +292,549 @@ Tracked browser settings cannot be saved because the AppHost does not have user secrets configured. + + Optional cookie domain used when setting or clearing a cookie. + Optional cookie domain used when setting or clearing a cookie. + + + + Cookie domain + Cookie domain + + + + Optional cookie name. Required when setting a cookie. + Optional cookie name. Required when setting a cookie. + + + + Cookie name + Cookie name + + + + Optional cookie path used when setting or clearing a cookie. Defaults to /. + Optional cookie path used when setting or clearing a cookie. Defaults to /. + + + + Cookie path + Cookie path + + + + Cookie value to set. Empty text clears the cookie value. + Cookie value to set. Empty text clears the cookie value. + + + + Cookie value + Cookie value + + + + Cookie action to perform: get, set, or clear. + Cookie action to perform: get, set, or clear. + + + + Get, set, or clear cookies visible to the active tracked browser page. + Get, set, or clear cookies visible to the active tracked browser page. + + + + Manage browser cookies + Manage browser cookies + + + + Updated browser cookies. + Updated browser cookies. + + + + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + Horizontal scroll distance in CSS pixels. Positive values scroll right and negative values scroll left. + + + + Delta X + Delta X + + + + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + Vertical scroll distance in CSS pixels. Positive values scroll down and negative values scroll up. + + + + Delta Y + Delta Y + + + + Dialog action to perform: accept or dismiss. + Dialog action to perform: accept or dismiss. + + + + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + Accept or dismiss the currently open JavaScript dialog in the active tracked browser page. + + + + Handle browser dialog + Handle browser dialog + + + + Handled browser dialog. + Handled browser dialog. + + + + Double-click an element in the active tracked browser page using a CSS selector. + Double-click an element in the active tracked browser page using a CSS selector. + + + + Double-click browser element + Double-click browser element + + + + Double-clicked browser element. + Double-clicked browser element. + + + + Browser download behavior: allow, allowAndName, deny, or default. + Browser download behavior: allow, allowAndName, deny, or default. + + + + Download behavior + Download behavior + + + + Enable browser download progress events when supported by the browser. + Enable browser download progress events when supported by the browser. + + + + Download events enabled + Download events enabled + + + + Directory path for downloaded files. Required for allow and allowAndName behavior. + Directory path for downloaded files. Required for allow and allowAndName behavior. + + + + Download path + Download path + + + + Configure download behavior for the tracked browser. + Configure download behavior for the tracked browser. + + + + Configure browser downloads + Configure browser downloads + + + + Configured browser downloads. + Configured browser downloads. + + + + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + Element state to wait for: attached, detached, visible, hidden, enabled, disabled, checked, or unchecked. + + + + Element state + Element state + + + + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + Evaluate a JavaScript expression in the active tracked browser page and return the JSON-serializable result. + + + + Evaluate browser script + Evaluate browser script + + + + Evaluated browser script. + Evaluated browser script. + + + + JavaScript expression or zero-argument function to evaluate in the browser page. + JavaScript expression or zero-argument function to evaluate in the browser page. + + + + Expression + Expression + + + + File path to upload, or a JSON array of file paths for multiple files. + File path to upload, or a JSON array of file paths for multiple files. + + + + Files + Files + + + + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + Fill an input, textarea, or editable element in the active tracked browser page using a CSS selector. + + + + Fill browser field + Fill browser field + + + + Filled browser field. + Filled browser field. + + + + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + Find a browser element by role, text, label, placeholder, alt text, title, test ID, or selector position and return its snapshot details. + + + + Find browser element + Find browser element + + + + Found browser element. + Found browser element. + + + + Search text, role, test ID, or CSS selector depending on the find strategy. + Search text, role, test ID, or CSS selector depending on the find strategy. + + + + Find value + Find value + + + + Focus an element in the active tracked browser page using a CSS selector. + Focus an element in the active tracked browser page using a CSS selector. + + + + Focus browser element + Focus browser element + + + + Focused browser element. + Focused browser element. + + + + Navigate the active tracked browser page forward in its history. + Navigate the active tracked browser page forward in its history. + + + + Go forward + Go forward + + + + Navigated browser forward. + Navigated browser forward. + + + + List frame elements in the active tracked browser page. + List frame elements in the active tracked browser page. + + + + List browser frames + List browser frames + + + + Listed browser frames. + Listed browser frames. + + + + Capture beyond the current viewport when supported by the browser. + Capture beyond the current viewport when supported by the browser. + + + + Full page + Full page + + + + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + JavaScript expression or zero-argument function to evaluate until it returns a truthy value. + + + + Function + Function + + + + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + Return a structured value from the active tracked browser page, such as title, URL, text, HTML, field value, attribute, count, box, or styles. + + + + Get browser value + Get browser value + + + + Retrieved browser value. + Retrieved browser value. + + + + Highlight an element in the active tracked browser page for visual debugging. + Highlight an element in the active tracked browser page for visual debugging. + + + + Highlight browser element + Highlight browser element + + + + Highlighted browser element. + Highlighted browser element. + + + + Move the pointer over an element in the active tracked browser page using a CSS selector. + Move the pointer over an element in the active tracked browser page using a CSS selector. + + + + Hover browser element + Hover browser element + + + + Hovered browser element. + Hovered browser element. + + + + One-based element index used by nth find strategy. + One-based element index used by nth find strategy. + + + + Index + Index + + + + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + Return a JSON snapshot of the active tracked browser page, including URL, title, visible text, and actionable elements with selectors. + + + + Inspect browser page + Inspect browser page + + + + Captured browser page snapshot. + Captured browser page snapshot. + + + + Check whether a selected browser element is visible, enabled, or checked. + Check whether a selected browser element is visible, enabled, or checked. + + + + Check browser element state + Check browser element state + + + + Checked browser element state. + Checked browser element state. + + + + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + Keyboard key to press, such as Enter, Escape, Tab, or a single character. + + + + Key + Key + + + + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + Dispatch a keydown event on a selected or currently focused element in the active tracked browser page. + + + + Key down in browser + Key down in browser + + + + Dispatched browser keydown. + Dispatched browser keydown. + + + + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + Dispatch a keyup event on a selected or currently focused element in the active tracked browser page. + + + + Key up in browser + Key up in browser + + + + Dispatched browser keyup. + Dispatched browser keyup. + + + + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + Find strategy: role, text, label, placeholder, alt, title, testid, first, last, or nth. + + + + Kind + Kind + + + + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + Page load state to wait for: domcontentloaded, load, complete, or networkidle. + + + + Load state + Load state + + + + How the current browser URL should be compared with the URL argument. + How the current browser URL should be compared with the URL argument. + + + + Match + Match + + + + Maximum number of actionable elements to include in the page snapshot. + Maximum number of actionable elements to include in the page snapshot. + + + + Maximum elements + Maximum elements + + + + Maximum number of visible page text characters to include in the page snapshot. + Maximum number of visible page text characters to include in the page snapshot. + + + + Maximum text length + Maximum text length + + + + Mouse action to dispatch: move, down, up, click, or wheel. + Mouse action to dispatch: move, down, up, click, or wheel. + + + + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + Dispatch low-level mouse input at viewport coordinates in the active tracked browser page. + + + + Dispatch browser mouse input + Dispatch browser mouse input + + + + Dispatched browser mouse input. + Dispatched browser mouse input. + + + + Mouse button for down, up, and click actions. + Mouse button for down, up, and click actions. + + + + Mouse button + Mouse button + + + + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + Optional attribute name, CSS property name, accessible name, or other command-specific qualifier. + + + + Name + Name + + + + Navigate the active tracked browser page to an absolute URL. + Navigate the active tracked browser page to an absolute URL. + + + + Navigate browser + Navigate browser + + + + Navigated browser page. + Navigated browser page. + + - Open the app in a tracked browser session and stream browser logs to this resource. - Open the app in a tracked browser session and stream browser logs to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. + Open the app in a tracked browser session and stream browser diagnostics to this resource. @@ -167,6 +842,471 @@ Open tracked browser + + Press a key on a selected or currently focused element in the active tracked browser page. + Press a key on a selected or currently focused element in the active tracked browser page. + + + + Press browser key + Press browser key + + + + Pressed browser key. + Pressed browser key. + + + + Optional text to enter before accepting a prompt dialog. + Optional text to enter before accepting a prompt dialog. + + + + Prompt text + Prompt text + + + + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + Browser value to read: title, url, text, html, value, attr, count, box, or styles. + + + + Property + Property + + + + Reload the active tracked browser page. + Reload the active tracked browser page. + + + + Reload browser + Reload browser + + + + Reloaded browser. + Reloaded browser. + + + + Image format for the screenshot: png, jpeg, or webp. + Image format for the screenshot: png, jpeg, or webp. + + + + Screenshot format + Screenshot format + + + + Optional image quality from 0 to 100 for jpeg or webp screenshots. + Optional image quality from 0 to 100 for jpeg or webp screenshots. + + + + Screenshot quality + Screenshot quality + + + + Scroll the active tracked browser page or a selected scrollable element. + Scroll the active tracked browser page or a selected scrollable element. + + + + Scroll browser + Scroll browser + + + + Scrolled browser. + Scrolled browser. + + + + Scroll an element in the active tracked browser page into view. + Scroll an element in the active tracked browser page into view. + + + + Scroll element into view + Scroll element into view + + + + Scrolled browser element into view. + Scrolled browser element into view. + + + + Select an option in a browser select element by option value or visible text. + Select an option in a browser select element by option value or visible text. + + + + Select browser option + Select browser option + + + + Selected browser option. + Selected browser option. + + + + Option value or visible option text to select. + Option value or visible option text to select. + + + + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + CSS selector or snapshot ref for the target element. Use refs such as e1 returned by Inspect browser page when possible. + + + + Selector + Selector + + + + Return a fresh browser page snapshot with the action result for immediate verification. + Return a fresh browser page snapshot with the action result for immediate verification. + + + + Snapshot after + Snapshot after + + + + Browser state action to perform: get, set, or clear. + Browser state action to perform: get, set, or clear. + + + + Element state to check: visible, enabled, or checked. + Element state to check: visible, enabled, or checked. + + + + State + State + + + + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + Get, restore, or clear browser page state including cookies, localStorage, and sessionStorage. + + + + Manage browser state + Manage browser state + + + + Updated browser state. + Updated browser state. + + + + Browser state JSON returned by the get action. Required when setting browser state. + Browser state JSON returned by the get action. Required when setting browser state. + + + + State JSON + State JSON + + + + Storage action to perform: get, set, or clear. + Storage action to perform: get, set, or clear. + + + + Browser storage area: local or session. + Browser storage area: local or session. + + + + Storage area + Storage area + + + + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + Get, set, or clear localStorage or sessionStorage entries for the active tracked browser page. + + + + Manage browser storage + Manage browser storage + + + + Updated browser storage. + Updated browser storage. + + + + Optional storage key. Required when setting a storage value. + Optional storage key. Required when setting a storage value. + + + + Storage key + Storage key + + + + Storage value to set. Empty text stores an empty string. + Storage value to set. Empty text stores an empty string. + + + + Storage value + Storage value + + + + Absolute URL for a new tab. Required when opening a tab. + Absolute URL for a new tab. Required when opening a tab. + + + + Tab action to perform: list, open, or close. + Tab action to perform: list, open, or close. + + + + List, open, or close browser page targets in the tracked browser. + List, open, or close browser page targets in the tracked browser. + + + + Manage browser tabs + Manage browser tabs + + + + Managed browser tabs. + Managed browser tabs. + + + + Browser target ID. Required when closing a tab. + Browser target ID. Required when closing a tab. + + + + Target ID + Target ID + + + + Visible page text to wait for. + Visible page text to wait for. + + + + Text + Text + + + + Maximum time to wait before the browser command fails. + Maximum time to wait before the browser command fails. + + + + Timeout (milliseconds) + Timeout (milliseconds) + + + + Type text into an input, textarea, or editable element in the active tracked browser page. + Type text into an input, textarea, or editable element in the active tracked browser page. + + + + Type browser text + Type browser text + + + + Typed browser text. + Typed browser text. + + + + Text to type into the target element. + Text to type into the target element. + + + + Uncheck a checkbox or other checkable element in the active tracked browser page. + Uncheck a checkbox or other checkable element in the active tracked browser page. + + + + Uncheck browser element + Uncheck browser element + + + + Unchecked browser element. + Unchecked browser element. + + + + Attach one or more local files to a file input in the active tracked browser page. + Attach one or more local files to a file input in the active tracked browser page. + + + + Upload browser files + Upload browser files + + + + Uploaded browser files. + Uploaded browser files. + + + + Absolute URL to navigate to. + Absolute URL to navigate to. + + + + URL + URL + + + + URL fragment to wait for in the active browser page. + URL fragment to wait for in the active browser page. + + + + URL contains + URL contains + + + + Text value to assign to the target field. + Text value to assign to the target field. + + + + Value + Value + + + + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + Wait until a browser condition is met. Supports selectors, text, URL matches, load states, element states, and JavaScript predicates. + + + + Wait + Wait + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until a selector is visible, text appears on the page, or both conditions are met. + Wait until a selector is visible, text appears on the page, or both conditions are met. + + + + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + Wait until a selected element reaches a state such as visible, hidden, enabled, disabled, checked, or unchecked. + + + + Wait for browser element state + Wait for browser element state + + + + Browser element state wait condition was met. + Browser element state wait condition was met. + + + + Wait until the active tracked browser page reaches a load state. + Wait until the active tracked browser page reaches a load state. + + + + Wait for browser load state + Wait for browser load state + + + + Browser load state wait condition was met. + Browser load state wait condition was met. + + + + Wait for browser page + Wait for browser page + + + + Browser wait condition was met. + Browser wait condition was met. + + + + Wait until the active tracked browser page URL matches the expected value. + Wait until the active tracked browser page URL matches the expected value. + + + + Wait for browser URL + Wait for browser URL + + + + Browser URL wait condition was met. + Browser URL wait condition was met. + + + + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + URL value to wait for. It can be a full URL, URL fragment, or regular expression depending on the match mode. + + + + Viewport X coordinate in CSS pixels. + Viewport X coordinate in CSS pixels. + + + + X coordinate + X coordinate + + + + Viewport Y coordinate in CSS pixels. + Viewport Y coordinate in CSS pixels. + + + + Y coordinate + Y coordinate + + \ No newline at end of file diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.cs.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.cs.xlf index 396592d598c..f74bf6603e6 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.cs.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.cs.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.de.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.de.xlf index d9c5fc6e058..8140000d9da 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.de.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.de.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.es.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.es.xlf index 16cd13c66ae..42e2a0bc4cc 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.es.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.es.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.fr.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.fr.xlf index 1de84eb879c..cf73f3723db 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.fr.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.fr.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.it.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.it.xlf index 487ed8b3c31..e488c95a7c6 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.it.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.it.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ja.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ja.xlf index c82e00bb947..8747384bd56 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ja.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ja.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ko.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ko.xlf index 74011373c69..c458c1b37a4 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ko.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ko.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pl.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pl.xlf index 767b341beb9..c0a066c4d93 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pl.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pl.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pt-BR.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pt-BR.xlf index 7f3c7a7faca..3f7ebc930cc 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pt-BR.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.pt-BR.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ru.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ru.xlf index 02389a86b46..e6a3a134757 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ru.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.ru.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.tr.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.tr.xlf index a6ed6dfa479..3a66efdc2db 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.tr.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.tr.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hans.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hans.xlf index 0e3bf14804e..6c502fd16e2 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hans.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hans.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hant.xlf b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hant.xlf index 52ccbd75fe3..cb7f2801313 100644 --- a/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hant.xlf +++ b/src/Aspire.Hosting.Browsers/Resources/xlf/BrowserMessageStrings.zh-Hant.xlf @@ -2,6 +2,11 @@ + + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to. + {0} is the Aspire resource name + Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. Browser profile '{0}' matched multiple Chromium profiles under '{1}'. Specify the profile directory name instead. @@ -52,11 +57,6 @@ Tracked browser configuration set '{0}' to '{1}' while '{2}' is '{3}'. Profiles can only be selected when '{2}' is '{4}'. {0} is the profile configuration key, {1} is the configured profile value, {2} is the user data mode configuration key, {3} is the configured user data mode, {4} is the required user data mode - - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - Resource '{0}' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to. - {0} is the Aspire resource name - A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. A tracked browser is already running for user data directory '{0}' with profile '{1}'. The requested profile is '{2}'. Close the existing tracked browser session or use isolated user data mode. diff --git a/src/Aspire.Hosting/Dashboard/DashboardService.cs b/src/Aspire.Hosting/Dashboard/DashboardService.cs index 84fc31bf615..28ba798077c 100644 --- a/src/Aspire.Hosting/Dashboard/DashboardService.cs +++ b/src/Aspire.Hosting/Dashboard/DashboardService.cs @@ -415,7 +415,7 @@ public override async Task ExecuteResourceCommand(Resou // ResourceCommandRequest arguments are encoded as a google.protobuf.Value in the dashboard gRPC protocol: // { // "command_name": "click", - // "resource_name": "web-browser-logs", + // "resource_name": "web-browser-automation", // "arguments": { "selector": "#submit" } // } // diff --git a/src/Shared/Model/KnownProperties.cs b/src/Shared/Model/KnownProperties.cs index db917b32720..16d628880c6 100644 --- a/src/Shared/Model/KnownProperties.cs +++ b/src/Shared/Model/KnownProperties.cs @@ -8,7 +8,7 @@ namespace Aspire.Dashboard.Model; /// /// /// Used as keys in the "properties" dictionary on resource snapshots and view models. -/// Should be compared using . +/// Should be compared using StringComparers.ResourcePropertyName. /// internal static class KnownProperties { diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs index d36a8f594a2..d88073aeadf 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Pages/ConsoleLogsTests.cs @@ -6,18 +6,24 @@ using Aspire.Dashboard.Components.Resize; using Aspire.Dashboard.Components.Tests.Shared; using Aspire.Dashboard.Model; +using Aspire.Dashboard.Model.Interaction; using Aspire.Dashboard.Tests.Shared; using Aspire.Dashboard.Utils; using Aspire.Shared.ConsoleLogs; +using Aspire.Tests.Shared; using Aspire.Tests.Shared.DashboardModel; using Bunit; +using Google.Protobuf.WellKnownTypes; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.FluentUI.AspNetCore.Components; using Xunit; +using DashboardInputType = Aspire.DashboardService.Proto.V1.InputType; +using DashboardInteractionInput = Aspire.DashboardService.Proto.V1.InteractionInput; namespace Aspire.Dashboard.Components.Tests.Pages; @@ -647,6 +653,82 @@ public void MenuButtons_SelectedResourceChanged_ButtonsUpdated() }); } + [Fact] + public async Task ExecuteCommand_WithArgumentInputs_SubmitsArgumentsFromDialog() + { + var testResource = ModelTestHelpers.CreateResource( + resourceName: "test-resource", + state: KnownResourceState.Running, + commands: + [ + new CommandViewModel( + "click-browser", + CommandViewModelState.Enabled, + "Click", + "Click an element", + confirmationMessage: "", + argumentInputs: + [ + new DashboardInteractionInput { Name = "selector", Label = "Selector", InputType = DashboardInputType.Text, Required = true }, + new DashboardInteractionInput { Name = "snapshotAfter", Label = "Snapshot after", InputType = DashboardInputType.Boolean } + ], + isHighlighted: true, + iconName: string.Empty, + iconVariant: IconVariant.Regular) + ]); + var subscribedResourceNameTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var commandArgumentsTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var consoleLogsChannel = Channel.CreateUnbounded>(); + var resourceChannel = Channel.CreateUnbounded>(); + var dashboardClient = new TestDashboardClient( + isEnabled: true, + consoleLogsChannelProvider: name => + { + subscribedResourceNameTcs.TrySetResult(name); + return consoleLogsChannel; + }, + resourceChannelProvider: () => resourceChannel, + executeResourceCommand: (_, _, _, arguments, _) => + { + commandArgumentsTcs.TrySetResult(arguments); + return Task.FromResult(new ResourceCommandResponseViewModel { Kind = ResourceCommandResponseKind.Succeeded }); + }, + initialResources: [testResource]); + + SetupConsoleLogsServices(dashboardClient); + Services.RemoveAll(); + Services.AddSingleton(new TestDialogService(onShowDialog: async (data, _) => + { + var viewModel = Assert.IsType(data); + viewModel.Interaction.InputsDialog.InputItems.Single(i => i.Name == "selector").Value = "#submit"; + viewModel.Interaction.InputsDialog.InputItems.Single(i => i.Name == "snapshotAfter").Value = "true"; + + await viewModel.OnSubmitCallback(viewModel.Interaction, false).ConfigureAwait(false); + return new DialogReference("arguments", null!); + })); + + var dimensionManager = Services.GetRequiredService(); + var viewport = new ViewportInformation(IsDesktop: true, IsUltraLowHeight: false, IsUltraLowWidth: false); + dimensionManager.InvokeOnViewportInformationChanged(viewport); + + var cut = RenderComponent(builder => + { + builder.Add(p => p.ResourceName, "test-resource"); + builder.Add(p => p.ViewportInformation, viewport); + }); + + cut.WaitForState(() => cut.Instance.PageViewModel.SelectedResource.Id?.InstanceId == testResource.Name); + var highlightedCommand = Assert.Single(cut.FindAll(".highlighted-command")); + + highlightedCommand.Click(); + + var arguments = await commandArgumentsTcs.Task.DefaultTimeout(); + Assert.NotNull(arguments); + Assert.Equal(Value.KindOneofCase.StructValue, arguments.KindCase); + Assert.Equal("#submit", arguments.StructValue.Fields["selector"].StringValue); + Assert.True(arguments.StructValue.Fields["snapshotAfter"].BoolValue); + } + [Fact] public async Task ExecuteCommand_DelayExecuting_IsExecutingReturnsTrueWhileRunning() { diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsBuilderExtensionsTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserAutomationBuilderExtensionsTests.cs similarity index 51% rename from tests/Aspire.Hosting.Browsers.Tests/BrowserLogsBuilderExtensionsTests.cs rename to tests/Aspire.Hosting.Browsers.Tests/BrowserAutomationBuilderExtensionsTests.cs index c6bfe45f596..8fedfb7206e 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsBuilderExtensionsTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserAutomationBuilderExtensionsTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. #pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. #pragma warning disable ASPIREUSERSECRETS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. @@ -24,10 +24,10 @@ namespace Aspire.Hosting.Browsers.Tests; [Trait("Partition", "2")] -public class BrowserLogsBuilderExtensionsTests(ITestOutputHelper testOutputHelper) +public class BrowserAutomationBuilderExtensionsTests(ITestOutputHelper testOutputHelper) { [Fact] - public void WithBrowserLogs_CreatesChildResource() + public void WithBrowserAutomation_CreatesChildResource() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); @@ -41,54 +41,146 @@ public void WithBrowserLogs_CreatesChildResource() Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); var appModel = app.Services.GetRequiredService(); - var browserLogsResource = Assert.Single(appModel.Resources.OfType()); - Assert.Equal("web-browser-logs", browserLogsResource.Name); - Assert.Equal(web.Resource.Name, browserLogsResource.ParentResource.Name); - Assert.Equal("chrome", browserLogsResource.InitialConfiguration.Browser); - Assert.Null(browserLogsResource.InitialConfiguration.Profile); - Assert.Contains(browserLogsResource.Annotations.OfType(), static annotation => annotation == NameValidationPolicyAnnotation.None); + var browserAutomationResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("web-browser-automation", browserAutomationResource.Name); + Assert.Equal(web.Resource.Name, browserAutomationResource.ParentResource.Name); + Assert.Equal("chrome", browserAutomationResource.InitialConfiguration.Browser); + Assert.Null(browserAutomationResource.InitialConfiguration.Profile); + Assert.Contains(browserAutomationResource.Annotations.OfType(), static annotation => annotation == NameValidationPolicyAnnotation.None); - Assert.True(browserLogsResource.TryGetAnnotationsOfType(out var relationships)); + Assert.True(browserAutomationResource.TryGetAnnotationsOfType(out var relationships)); var parentRelationship = Assert.Single(relationships, relationship => relationship.Type == "Parent"); Assert.Equal(web.Resource.Name, parentRelationship.Resource.Name); - var command = Assert.Single(browserLogsResource.Annotations.OfType(), annotation => annotation.Name == BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName); + var command = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName); Assert.Equal(BrowserCommandStrings.OpenTrackedBrowserName, command.DisplayName); Assert.Equal(BrowserCommandStrings.OpenTrackedBrowserDescription, command.DisplayDescription); - var configureCommand = Assert.Single(browserLogsResource.Annotations.OfType(), annotation => annotation.Name == BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + Assert.True(command.Visibility.HasFlag(ResourceCommandVisibility.Dashboard)); + Assert.True(command.Visibility.HasFlag(ResourceCommandVisibility.Api)); + var configureCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); Assert.Equal(BrowserCommandStrings.ConfigureTrackedBrowserName, configureCommand.DisplayName); Assert.Equal(BrowserCommandStrings.ConfigureTrackedBrowserDescription, configureCommand.DisplayDescription); - var screenshotCommand = Assert.Single(browserLogsResource.Annotations.OfType(), annotation => annotation.Name == BrowserLogsBuilderExtensions.CaptureScreenshotCommandName); + Assert.True(configureCommand.Visibility.HasFlag(ResourceCommandVisibility.Dashboard)); + Assert.True(configureCommand.Visibility.HasFlag(ResourceCommandVisibility.Api)); + var screenshotCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.CaptureScreenshotCommandName); Assert.Equal(BrowserCommandStrings.CaptureScreenshotName, screenshotCommand.DisplayName); Assert.Equal(BrowserCommandStrings.CaptureScreenshotDescription, screenshotCommand.DisplayDescription); - - var snapshot = browserLogsResource.Annotations.OfType().Single().InitialSnapshot; - Assert.Equal(BrowserLogsBuilderExtensions.BrowserResourceType, snapshot.ResourceType); + Assert.True(screenshotCommand.Visibility.HasFlag(ResourceCommandVisibility.Dashboard)); + Assert.True(screenshotCommand.Visibility.HasFlag(ResourceCommandVisibility.Api)); + var inspectCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.InspectBrowserCommandName); + Assert.Equal(BrowserCommandStrings.InspectBrowserName, inspectCommand.DisplayName); + Assert.Contains(inspectCommand.ArgumentInputs!, input => input.Name == "maxElements" && input.InputType == InputType.Number && input.Required == false); + Assert.Equal(ResourceCommandVisibility.Api, inspectCommand.Visibility); + var getCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.GetCommandName); + Assert.Contains(getCommand.ArgumentInputs!, input => input.Name == "property" && input.InputType == InputType.Choice && input.Value == "text"); + var isCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.IsCommandName); + Assert.Contains(isCommand.ArgumentInputs!, input => input.Name == "state" && input.InputType == InputType.Choice && input.Value == "visible"); + var findCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.FindCommandName); + Assert.Contains(findCommand.ArgumentInputs!, input => input.Name == "kind" && input.InputType == InputType.Choice && input.Value == "text"); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.HighlightCommandName); + var evaluateCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.EvaluateCommandName); + Assert.Contains(evaluateCommand.ArgumentInputs!, input => input.Name == "expression" && input.InputType == InputType.Text && input.Required); + var cookiesCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.CookiesCommandName); + Assert.Contains(cookiesCommand.ArgumentInputs!, input => input.Name == "action" && input.InputType == InputType.Choice && input.Value == "get"); + var storageCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.StorageCommandName); + Assert.Contains(storageCommand.ArgumentInputs!, input => input.Name == "area" && input.InputType == InputType.Choice && input.Value == "local"); + var stateCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.StateCommandName); + Assert.Contains(stateCommand.ArgumentInputs!, input => input.Name == "clearExisting" && input.InputType == InputType.Boolean && !input.Required); + var cdpCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.CdpCommandName); + Assert.Contains(cdpCommand.ArgumentInputs!, input => input.Name == "method" && input.InputType == InputType.Text && input.Required); + Assert.Contains(cdpCommand.ArgumentInputs!, input => input.Name == "session" && input.InputType == InputType.Choice && input.Value == "page"); + var tabsCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.TabsCommandName); + Assert.Contains(tabsCommand.ArgumentInputs!, input => input.Name == "action" && input.InputType == InputType.Choice && input.Value == "list"); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.FramesCommandName); + var dialogCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.DialogCommandName); + Assert.Contains(dialogCommand.ArgumentInputs!, input => input.Name == "action" && input.InputType == InputType.Choice && input.Value == "accept"); + var downloadsCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.DownloadsCommandName); + Assert.Contains(downloadsCommand.ArgumentInputs!, input => input.Name == "behavior" && input.InputType == InputType.Choice && input.Value == "allow"); + var uploadCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.UploadCommandName); + Assert.Contains(uploadCommand.ArgumentInputs!, input => input.Name == "files" && input.InputType == InputType.Text && input.Required); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.BrowserUrlCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.BackBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.ForwardBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.ReloadBrowserCommandName); + var clickCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.ClickBrowserCommandName); + var selectorArgument = Assert.Single(clickCommand.ArgumentInputs!, input => input.Name == "selector"); + Assert.Equal(InputType.Text, selectorArgument.InputType); + Assert.True(selectorArgument.Required); + Assert.Contains(clickCommand.ArgumentInputs!, input => input.Name == "snapshotAfter" && input.InputType == InputType.Boolean && !input.Required); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.DoubleClickBrowserCommandName); + var fillCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.FillBrowserCommandName); + Assert.Contains(fillCommand.ArgumentInputs!, input => input.Name == "value" && input.InputType == InputType.Text && input.Required); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.CheckBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.UncheckBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.NavigateBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.FocusBrowserElementCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.TypeBrowserTextCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.PressBrowserKeyCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.KeyDownBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.KeyUpBrowserCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.HoverBrowserElementCommandName); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.SelectBrowserOptionCommandName); + var scrollCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.ScrollBrowserCommandName); + Assert.Contains(scrollCommand.ArgumentInputs!, input => input.Name == "deltaY" && input.InputType == InputType.Number && !input.Required); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.ScrollIntoViewBrowserCommandName); + var mouseCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.MouseBrowserCommandName); + Assert.Contains(mouseCommand.ArgumentInputs!, input => input.Name == "action" && input.InputType == InputType.Choice && input.Value == "move"); + Assert.Contains(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.WaitForBrowserCommandName); + var waitCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.WaitCommandName); + Assert.Contains(waitCommand.ArgumentInputs!, input => input.Name == "urlContains" && input.InputType == InputType.Text && !input.Required); + Assert.Contains(waitCommand.ArgumentInputs!, input => input.Name == "loadState" && input.InputType == InputType.Choice && input.Value is null); + Assert.Contains(waitCommand.ArgumentInputs!, input => input.Name == "function" && input.InputType == InputType.Text && !input.Required); + var waitUrlCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.WaitForBrowserUrlCommandName); + Assert.Contains(waitUrlCommand.ArgumentInputs!, input => input.Name == "match" && input.InputType == InputType.Choice && input.Value == "contains"); + var waitLoadStateCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.WaitForBrowserLoadStateCommandName); + Assert.Contains(waitLoadStateCommand.ArgumentInputs!, input => input.Name == "state" && input.InputType == InputType.Choice && input.Value == "load"); + var waitElementStateCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.WaitForBrowserElementStateCommandName); + Assert.Contains(waitElementStateCommand.ArgumentInputs!, input => input.Name == "state" && input.InputType == InputType.Choice && input.Value == "visible"); + var closeCommand = Assert.Single(browserAutomationResource.Annotations.OfType(), annotation => annotation.Name == BrowserAutomationBuilderExtensions.CloseTrackedBrowserCommandName); + Assert.True(closeCommand.Visibility.HasFlag(ResourceCommandVisibility.Dashboard)); + Assert.True(closeCommand.Visibility.HasFlag(ResourceCommandVisibility.Api)); + + var dashboardCommandNames = browserAutomationResource.Annotations.OfType() + .Where(annotation => annotation.Visibility.HasFlag(ResourceCommandVisibility.Dashboard)) + .Select(annotation => annotation.Name) + .Order(StringComparer.Ordinal) + .ToArray(); + Assert.Equal( + [ + BrowserAutomationBuilderExtensions.CaptureScreenshotCommandName, + BrowserAutomationBuilderExtensions.CloseTrackedBrowserCommandName, + BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName, + BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName + ], + dashboardCommandNames); + + var snapshot = browserAutomationResource.Annotations.OfType().Single().InitialSnapshot; + Assert.Equal(BrowserAutomationBuilderExtensions.BrowserResourceType, snapshot.ResourceType); Assert.NotNull(snapshot.CreationTimeStamp); Assert.Contains(snapshot.Properties, property => property.Name == CustomResourceKnownProperties.Source && Equals(property.Value, "web")); - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.BrowserPropertyName && Equals(property.Value, "chrome")); - Assert.DoesNotContain(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.ProfilePropertyName); - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName && Equals(property.Value, 0)); - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.ActiveSessionsPropertyName && Equals(property.Value, "None")); - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.BrowserSessionsPropertyName && Equals(property.Value, "[]")); - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.TotalSessionsLaunchedPropertyName && Equals(property.Value, 0)); + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.BrowserPropertyName && Equals(property.Value, "chrome")); + Assert.DoesNotContain(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.ProfilePropertyName); + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName && Equals(property.Value, 0)); + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName && Equals(property.Value, "None")); + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.BrowserSessionsPropertyName && Equals(property.Value, "[]")); + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.TotalSessionsLaunchedPropertyName && Equals(property.Value, 0)); Assert.Empty(snapshot.HealthReports); } [Fact] - public void WithBrowserLogs_UsesResourceSpecificConfigurationWhenArgumentsAreOmitted() + public void WithBrowserAutomation_UsesResourceSpecificConfigurationWhenArgumentsAreOmitted() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "msedge"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"] = "Default"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "msedge"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = "Default"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; var web = builder.AddResource(new TestHttpResource("web")) .WithHttpEndpoint(targetPort: 8080) @@ -100,17 +192,45 @@ public void WithBrowserLogs_UsesResourceSpecificConfigurationWhenArgumentsAreOmi Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); - var browserLogsResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + + Assert.Equal("chrome", browserAutomationResource.InitialConfiguration.Browser); + Assert.Equal("Profile 1", browserAutomationResource.InitialConfiguration.Profile); + + var snapshot = browserAutomationResource.Annotations.OfType().Single().InitialSnapshot; + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.BrowserPropertyName && Equals(property.Value, "chrome")); + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.ProfilePropertyName && Equals(property.Value, "Profile 1")); + } + + [Fact] + public void WithBrowserAutomation_UsesLegacyBrowserLogsConfigurationWhenAutomationConfigurationIsMissing() + { + using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.LegacyBrowserLogsConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "msedge"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.LegacyBrowserLogsConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.LegacyBrowserLogsConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.LegacyBrowserLogsConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; + + var web = builder.AddResource(new TestHttpResource("web")) + .WithHttpEndpoint(targetPort: 8080) + .WithEndpoint("http", endpoint => endpoint.AllocatedEndpoint = new AllocatedEndpoint(endpoint, "localhost", 8080)) + .WithInitialState(new CustomResourceSnapshot + { + ResourceType = "TestHttp", + State = new ResourceStateSnapshot(KnownResourceStates.Running, KnownResourceStateStyles.Success), + Properties = [] + }); - Assert.Equal("chrome", browserLogsResource.InitialConfiguration.Browser); - Assert.Equal("Profile 1", browserLogsResource.InitialConfiguration.Profile); + web.WithBrowserAutomation(); - var snapshot = browserLogsResource.Annotations.OfType().Single().InitialSnapshot; - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.BrowserPropertyName && Equals(property.Value, "chrome")); - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.ProfilePropertyName && Equals(property.Value, "Profile 1")); + using var app = builder.Build(); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + + Assert.Equal("chrome", browserAutomationResource.InitialConfiguration.Browser); + Assert.Equal("Profile 1", browserAutomationResource.InitialConfiguration.Profile); } [Fact] @@ -163,7 +283,7 @@ public void GetDefaultBrowser_FallsBackToChromeWhenKnownBrowsersAreMissing() } [Fact] - public void WithBrowserLogs_UsesDetectedDefaultBrowserWhenConfigurationIsMissing() + public void WithBrowserAutomation_UsesDetectedDefaultBrowserWhenConfigurationIsMissing() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); @@ -177,22 +297,22 @@ public void WithBrowserLogs_UsesDetectedDefaultBrowserWhenConfigurationIsMissing Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); - var browserLogsResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); - Assert.Equal(BrowserConfiguration.GetDefaultBrowser(ChromiumBrowserResolver.TryResolveExecutable), browserLogsResource.InitialConfiguration.Browser); - Assert.Null(browserLogsResource.InitialConfiguration.Profile); + Assert.Equal(BrowserConfiguration.GetDefaultBrowser(ChromiumBrowserResolver.TryResolveExecutable), browserAutomationResource.InitialConfiguration.Browser); + Assert.Null(browserAutomationResource.InitialConfiguration.Profile); } [Fact] - public void WithBrowserLogs_ExplicitArgumentsOverrideConfiguration() + public void WithBrowserAutomation_ExplicitArgumentsOverrideConfiguration() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); var web = builder.AddResource(new TestHttpResource("web")) .WithHttpEndpoint(targetPort: 8080) @@ -204,18 +324,18 @@ public void WithBrowserLogs_ExplicitArgumentsOverrideConfiguration() Properties = [] }); - web.WithBrowserLogs(browser: "msedge", profile: "Default", userDataMode: BrowserUserDataMode.Shared); + web.WithBrowserAutomation(browser: "msedge", profile: "Default", userDataMode: BrowserUserDataMode.Shared); using var app = builder.Build(); - var browserLogsResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); - Assert.Equal("msedge", browserLogsResource.InitialConfiguration.Browser); - Assert.Equal("Default", browserLogsResource.InitialConfiguration.Profile); - Assert.Equal(BrowserUserDataMode.Shared, browserLogsResource.InitialConfiguration.UserDataMode); + Assert.Equal("msedge", browserAutomationResource.InitialConfiguration.Browser); + Assert.Equal("Default", browserAutomationResource.InitialConfiguration.Profile); + Assert.Equal(BrowserUserDataMode.Shared, browserAutomationResource.InitialConfiguration.UserDataMode); } [Fact] - public void WithBrowserLogs_DefaultsToSharedUserDataMode() + public void WithBrowserAutomation_DefaultsToSharedUserDataMode() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); var web = builder.AddResource(new TestHttpResource("web")) @@ -228,21 +348,21 @@ public void WithBrowserLogs_DefaultsToSharedUserDataMode() Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); - var browserLogsResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); - Assert.Equal(BrowserUserDataMode.Shared, browserLogsResource.InitialConfiguration.UserDataMode); - var snapshot = browserLogsResource.Annotations.OfType().Single().InitialSnapshot; - Assert.Contains(snapshot.Properties, property => property.Name == BrowserLogsBuilderExtensions.UserDataModePropertyName && Equals(property.Value, nameof(BrowserUserDataMode.Shared))); + Assert.Equal(BrowserUserDataMode.Shared, browserAutomationResource.InitialConfiguration.UserDataMode); + var snapshot = browserAutomationResource.Annotations.OfType().Single().InitialSnapshot; + Assert.Contains(snapshot.Properties, property => property.Name == BrowserAutomationBuilderExtensions.UserDataModePropertyName && Equals(property.Value, nameof(BrowserUserDataMode.Shared))); } [Fact] - public void WithBrowserLogs_ReadsUserDataModeFromConfiguration() + public void WithBrowserAutomation_ReadsUserDataModeFromConfiguration() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); var web = builder.AddResource(new TestHttpResource("web")) .WithHttpEndpoint(targetPort: 8080) @@ -254,16 +374,16 @@ public void WithBrowserLogs_ReadsUserDataModeFromConfiguration() Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); - var browserLogsResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); - Assert.Equal(BrowserUserDataMode.Shared, browserLogsResource.InitialConfiguration.UserDataMode); + Assert.Equal(BrowserUserDataMode.Shared, browserAutomationResource.InitialConfiguration.UserDataMode); } [Fact] - public void WithBrowserLogs_RejectsProfileWhenUserDataModeIsIsolated() + public void WithBrowserAutomation_RejectsProfileWhenUserDataModeIsIsolated() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); var web = builder.AddResource(new TestHttpResource("web")) @@ -277,15 +397,15 @@ public void WithBrowserLogs_RejectsProfileWhenUserDataModeIsIsolated() }); var ex = Assert.Throws( - () => web.WithBrowserLogs(profile: "Default", userDataMode: BrowserUserDataMode.Isolated)); - Assert.Contains(BrowserLogsBuilderExtensions.UserDataModeConfigurationKey, ex.Message); + () => web.WithBrowserAutomation(profile: "Default", userDataMode: BrowserUserDataMode.Isolated)); + Assert.Contains(BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey, ex.Message); } [Fact] - public void WithBrowserLogs_ExplicitUserDataModeOverridesConfiguration() + public void WithBrowserAutomation_ExplicitUserDataModeOverridesConfiguration() { using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Run); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Isolated); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Isolated); var web = builder.AddResource(new TestHttpResource("web")) .WithHttpEndpoint(targetPort: 8080) @@ -297,15 +417,15 @@ public void WithBrowserLogs_ExplicitUserDataModeOverridesConfiguration() Properties = [] }); - web.WithBrowserLogs(userDataMode: BrowserUserDataMode.Shared); + web.WithBrowserAutomation(userDataMode: BrowserUserDataMode.Shared); using var app = builder.Build(); - var browserLogsResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); - Assert.Equal(BrowserUserDataMode.Shared, browserLogsResource.InitialConfiguration.UserDataMode); + var browserAutomationResource = Assert.Single(app.Services.GetRequiredService().Resources.OfType()); + Assert.Equal(BrowserUserDataMode.Shared, browserAutomationResource.InitialConfiguration.UserDataMode); } [Fact] - public async Task WithBrowserLogs_CommandStartsTrackedSession() + public async Task WithBrowserAutomation_CommandStartsTrackedSession() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionManager = new FakeBrowserLogsSessionManager(); @@ -321,26 +441,26 @@ public async Task WithBrowserLogs_CommandStartsTrackedSession() Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(result.Success); var call = Assert.Single(sessionManager.Calls); - Assert.Same(browserLogsResource, call.Resource); - Assert.Equal(browserLogsResource.Name, call.ResourceName); + Assert.Same(browserAutomationResource, call.Resource); + Assert.Equal(browserAutomationResource.Name, call.ResourceName); Assert.Equal("chrome", call.Configuration.Browser); Assert.Null(call.Configuration.Profile); Assert.Equal(new Uri("http://localhost:8080", UriKind.Absolute), call.Url); } [Fact] - public async Task WithBrowserLogs_ConfigureCommandSavesResourceScopedBrowserSettingsAndAppliesImmediately() + public async Task WithBrowserAutomation_ConfigureCommandSavesResourceScopedBrowserSettingsAndAppliesImmediately() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); @@ -359,13 +479,13 @@ public async Task WithBrowserLogs_ConfigureCommandSavesResourceScopedBrowserSett Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); Assert.Equal(BrowserCommandStrings.ConfigureTrackedBrowserName, interaction.Title); @@ -393,11 +513,11 @@ public async Task WithBrowserLogs_ConfigureCommandSavesResourceScopedBrowserSett var result = await commandTask.DefaultTimeout(); Assert.True(result.Success); - Assert.Equal("msedge", userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"]); - Assert.Equal(nameof(BrowserUserDataMode.Shared), userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"]); - Assert.Equal("Default", userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"]); + Assert.Equal("msedge", userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"]); + Assert.Equal(nameof(BrowserUserDataMode.Shared), userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"]); + Assert.Equal("Default", userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"]); - var effectiveConfiguration = browserLogsResource.ResolveCurrentConfiguration( + var effectiveConfiguration = browserAutomationResource.ResolveCurrentConfiguration( app.Services.GetRequiredService(), app.Services.GetRequiredService()); Assert.Equal("msedge", effectiveConfiguration.Browser); @@ -406,7 +526,7 @@ public async Task WithBrowserLogs_ConfigureCommandSavesResourceScopedBrowserSett } [Fact] - public async Task WithBrowserLogs_ConfigureCommandAppliesRuntimeSettingsWhenUserSecretsAreUnavailable() + public async Task WithBrowserAutomation_ConfigureCommandAppliesRuntimeSettingsWhenUserSecretsAreUnavailable() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); @@ -428,13 +548,13 @@ public async Task WithBrowserLogs_ConfigureCommandAppliesRuntimeSettingsWhenUser Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); var saveToUserSecrets = interaction.Inputs["saveToUserSecrets"]; @@ -455,7 +575,7 @@ public async Task WithBrowserLogs_ConfigureCommandAppliesRuntimeSettingsWhenUser Assert.Empty(userSecretsManager.Secrets); Assert.Empty(userSecretsManager.DeletedSecrets); - var effectiveConfiguration = browserLogsResource.ResolveCurrentConfiguration( + var effectiveConfiguration = browserAutomationResource.ResolveCurrentConfiguration( app.Services.GetRequiredService(), app.Services.GetRequiredService()); Assert.Equal("msedge", effectiveConfiguration.Browser); @@ -464,7 +584,7 @@ public async Task WithBrowserLogs_ConfigureCommandAppliesRuntimeSettingsWhenUser } [Fact] - public async Task WithBrowserLogs_ConfigureCommandDoesNotOverrideExplicitBuilderSettings() + public async Task WithBrowserAutomation_ConfigureCommandDoesNotOverrideExplicitBuilderSettings() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); @@ -483,13 +603,13 @@ public async Task WithBrowserLogs_ConfigureCommandDoesNotOverrideExplicitBuilder Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); interaction.Inputs["scope"].Value = "resource"; @@ -501,9 +621,9 @@ public async Task WithBrowserLogs_ConfigureCommandDoesNotOverrideExplicitBuilder var result = await commandTask.DefaultTimeout(); Assert.True(result.Success); - Assert.Equal("msedge", userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"]); + Assert.Equal("msedge", userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"]); - var effectiveConfiguration = browserLogsResource.ResolveCurrentConfiguration( + var effectiveConfiguration = browserAutomationResource.ResolveCurrentConfiguration( app.Services.GetRequiredService(), app.Services.GetRequiredService()); Assert.Equal("chrome", effectiveConfiguration.Browser); @@ -512,7 +632,7 @@ public async Task WithBrowserLogs_ConfigureCommandDoesNotOverrideExplicitBuilder } [Fact] - public async Task WithBrowserLogs_ConfigureCommandSavesGlobalSettingsAndClearsProfile() + public async Task WithBrowserAutomation_ConfigureCommandSavesGlobalSettingsAndClearsProfile() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); @@ -520,7 +640,7 @@ public async Task WithBrowserLogs_ConfigureCommandSavesGlobalSettingsAndClearsPr builder.Configuration[KnownConfigNames.VersionCheckDisabled] = "true"; builder.Services.AddSingleton(interactionService); builder.Services.AddSingleton(userSecretsManager); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = "Profile 1"; var web = builder.AddResource(new TestHttpResource("web")) .WithHttpEndpoint(targetPort: 8080) @@ -532,13 +652,13 @@ public async Task WithBrowserLogs_ConfigureCommandSavesGlobalSettingsAndClearsPr Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); interaction.Inputs["scope"].Value = "global"; @@ -550,11 +670,11 @@ public async Task WithBrowserLogs_ConfigureCommandSavesGlobalSettingsAndClearsPr var result = await commandTask.DefaultTimeout(); Assert.True(result.Success); - Assert.Equal("chrome", userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"]); - Assert.Equal(nameof(BrowserUserDataMode.Isolated), userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"]); - Assert.Contains($"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}", userSecretsManager.DeletedSecrets); + Assert.Equal("chrome", userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"]); + Assert.Equal(nameof(BrowserUserDataMode.Isolated), userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"]); + Assert.Contains($"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}", userSecretsManager.DeletedSecrets); - var effectiveConfiguration = browserLogsResource.ResolveCurrentConfiguration( + var effectiveConfiguration = browserAutomationResource.ResolveCurrentConfiguration( app.Services.GetRequiredService(), app.Services.GetRequiredService()); Assert.Equal("chrome", effectiveConfiguration.Browser); @@ -563,18 +683,18 @@ public async Task WithBrowserLogs_ConfigureCommandSavesGlobalSettingsAndClearsPr } [Fact] - public async Task WithBrowserLogs_ConfigureCommandDoesNotApplyRuntimeSettingsWhenUserSecretSaveFails() + public async Task WithBrowserAutomation_ConfigureCommandDoesNotApplyRuntimeSettingsWhenUserSecretSaveFails() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); var userSecretsManager = new RecordingUserSecretsManager(); builder.Configuration[KnownConfigNames.VersionCheckDisabled] = "true"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); builder.Services.AddSingleton(interactionService); builder.Services.AddSingleton(userSecretsManager); - var userDataModeKey = $"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"; + var userDataModeKey = $"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"; userSecretsManager.FailingSetSecretNames.Add(userDataModeKey); var web = builder.AddResource(new TestHttpResource("web")) @@ -587,13 +707,13 @@ public async Task WithBrowserLogs_ConfigureCommandDoesNotApplyRuntimeSettingsWhe Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); interaction.Inputs["scope"].Value = "resource"; @@ -606,10 +726,10 @@ public async Task WithBrowserLogs_ConfigureCommandDoesNotApplyRuntimeSettingsWhe Assert.False(result.Success); Assert.Contains(userDataModeKey, result.Message, StringComparison.Ordinal); - Assert.Equal("msedge", userSecretsManager.Secrets[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:web:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"]); + Assert.Equal("msedge", userSecretsManager.Secrets[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:web:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"]); Assert.DoesNotContain(userDataModeKey, userSecretsManager.Secrets.Keys); - var effectiveConfiguration = browserLogsResource.ResolveCurrentConfiguration( + var effectiveConfiguration = browserAutomationResource.ResolveCurrentConfiguration( app.Services.GetRequiredService(), app.Services.GetRequiredService()); Assert.Equal("chrome", effectiveConfiguration.Browser); @@ -618,7 +738,7 @@ public async Task WithBrowserLogs_ConfigureCommandDoesNotApplyRuntimeSettingsWhe } [Fact] - public async Task WithBrowserLogs_ConfigureCommandRefreshesAllBrowserLogsResourcesForGlobalSettings() + public async Task WithBrowserAutomation_ConfigureCommandRefreshesAllBrowserAutomationResourcesForGlobalSettings() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); @@ -647,16 +767,16 @@ public async Task WithBrowserLogs_ConfigureCommandRefreshesAllBrowserLogsResourc Properties = [] }); - web.WithBrowserLogs(); - admin.WithBrowserLogs(); + web.WithBrowserAutomation(); + admin.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResources = app.Services.GetRequiredService().Resources.OfType().ToArray(); - var webBrowserLogsResource = browserLogsResources.Single(resource => resource.ParentResource.Name == "web"); - var adminBrowserLogsResource = browserLogsResources.Single(resource => resource.ParentResource.Name == "admin"); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(webBrowserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResources = app.Services.GetRequiredService().Resources.OfType().ToArray(); + var webBrowserAutomationResource = browserAutomationResources.Single(resource => resource.ParentResource.Name == "web"); + var adminBrowserAutomationResource = browserAutomationResources.Single(resource => resource.ParentResource.Name == "admin"); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(webBrowserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); interaction.Inputs["scope"].Value = "global"; @@ -670,15 +790,15 @@ public async Task WithBrowserLogs_ConfigureCommandRefreshesAllBrowserLogsResourc Assert.True(result.Success); await app.ResourceNotifications.WaitForResourceAsync( - adminBrowserLogsResource.Name, + adminBrowserAutomationResource.Name, resourceEvent => - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserPropertyName, "chrome") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.UserDataModePropertyName, nameof(BrowserUserDataMode.Isolated)) && - DoesNotHaveProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ProfilePropertyName)).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserPropertyName, "chrome") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.UserDataModePropertyName, nameof(BrowserUserDataMode.Isolated)) && + DoesNotHaveProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ProfilePropertyName)).DefaultTimeout(); } [Fact] - public async Task WithBrowserLogs_ConfigureCommandValidatesEffectiveConfigurationBeforeSaving() + public async Task WithBrowserAutomation_ConfigureCommandValidatesEffectiveConfigurationBeforeSaving() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var interactionService = new TestInteractionService(); @@ -697,13 +817,13 @@ public async Task WithBrowserLogs_ConfigureCommandValidatesEffectiveConfiguratio Properties = [] }); - web.WithBrowserLogs(profile: "Default"); + web.WithBrowserAutomation(profile: "Default"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.ConfigureTrackedBrowserCommandName); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var commandTask = app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.ConfigureTrackedBrowserCommandName); var interaction = await interactionService.Interactions.Reader.ReadAsync().DefaultTimeout(); interaction.Inputs["scope"].Value = "resource"; @@ -719,7 +839,7 @@ public async Task WithBrowserLogs_ConfigureCommandValidatesEffectiveConfiguratio Assert.Empty(userSecretsManager.Secrets); Assert.Empty(userSecretsManager.DeletedSecrets); - var effectiveConfiguration = browserLogsResource.ResolveCurrentConfiguration( + var effectiveConfiguration = browserAutomationResource.ResolveCurrentConfiguration( app.Services.GetRequiredService(), app.Services.GetRequiredService()); Assert.Equal(BrowserUserDataMode.Shared, effectiveConfiguration.UserDataMode); @@ -727,7 +847,7 @@ public async Task WithBrowserLogs_ConfigureCommandValidatesEffectiveConfiguratio } [Fact] - public async Task WithBrowserLogs_CaptureScreenshotCommandReturnsArtifactResult() + public async Task WithBrowserAutomation_CaptureScreenshotCommandReturnsArtifactResult() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionManager = new FakeBrowserLogsSessionManager @@ -741,7 +861,7 @@ public async Task WithBrowserLogs_CaptureScreenshotCommandReturnsArtifactResult( "target-0002", new Uri("https://localhost:8443/"), new BrowserLogsArtifact( - "web-browser-logs", + "web-browser-automation", "screenshot", Path.Combine(AppContext.BaseDirectory, "artifacts", "screenshot.png"), "image/png", @@ -760,23 +880,25 @@ public async Task WithBrowserLogs_CaptureScreenshotCommandReturnsArtifactResult( Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.CaptureScreenshotCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + using var screenshotArguments = JsonDocument.Parse("""{"format":"jpeg","quality":80,"fullPage":true}"""); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.CaptureScreenshotCommandName, screenshotArguments.RootElement).DefaultTimeout(); Assert.True(result.Success); - Assert.Equal("web-browser-logs", Assert.Single(sessionManager.CaptureScreenshotCalls)); + Assert.Equal("web-browser-automation", Assert.Single(sessionManager.CaptureScreenshotCalls)); + Assert.Equal(new BrowserScreenshotCaptureOptions("jpeg", 80, FullPage: true), sessionManager.ScreenshotOptions.GetValueOrDefault()); Assert.Contains("screenshot.png", result.Message); Assert.NotNull(result.Data); Assert.Equal(CommandResultFormat.Json, result.Data.Format); Assert.True(result.Data.DisplayImmediately); using var document = JsonDocument.Parse(result.Data.Value); - Assert.Equal("web-browser-logs", document.RootElement.GetProperty("resourceName").GetString()); + Assert.Equal("web-browser-automation", document.RootElement.GetProperty("resourceName").GetString()); Assert.Equal("session-0002", document.RootElement.GetProperty("sessionId").GetString()); Assert.Equal("msedge", document.RootElement.GetProperty("browser").GetString()); Assert.Equal(@"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", document.RootElement.GetProperty("browserExecutable").GetString()); @@ -790,7 +912,128 @@ public async Task WithBrowserLogs_CaptureScreenshotCommandReturnsArtifactResult( } [Fact] - public async Task WithBrowserLogs_CaptureScreenshotCommandReturnsClearFailureWhenNoSessionIsActive() + public async Task WithBrowserAutomation_BrowserCommandsForwardArgumentsAndReturnJson() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + var sessionManager = new FakeBrowserLogsSessionManager(); + builder.Services.AddSingleton(sessionManager); + + var web = builder.AddResource(new TestHttpResource("web")) + .WithHttpEndpoint(targetPort: 8080) + .WithEndpoint("http", endpoint => endpoint.AllocatedEndpoint = new AllocatedEndpoint(endpoint, "localhost", 8080)) + .WithInitialState(new CustomResourceSnapshot + { + ResourceType = "TestHttp", + State = new ResourceStateSnapshot(KnownResourceStates.Running, KnownResourceStateStyles.Success), + Properties = [] + }); + + web.WithBrowserAutomation(browser: "chrome"); + + using var app = builder.Build(); + await app.StartAsync(); + + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.GetCommandName, """{"property":"attr","selector":"#link","name":"href"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.IsCommandName, """{"state":"visible","selector":"#submit"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.FindCommandName, """{"kind":"role","value":"button","name":"Submit","index":1}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.HighlightCommandName, """{"selector":"#submit"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.EvaluateCommandName, """{"expression":"document.title"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.CookiesCommandName, """{"action":"set","name":"session","value":"abc","path":"/"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.StorageCommandName, """{"area":"local","action":"set","key":"theme","value":"dark"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.StateCommandName, """{"action":"set","state":"{\"cookies\":[],\"localStorage\":{},\"sessionStorage\":{}}","clearExisting":true}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.CdpCommandName, """{"method":"Runtime.evaluate","params":"{\"expression\":\"document.title\",\"returnByValue\":true}","session":"page"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.TabsCommandName, """{"action":"open","url":"https://example.com/"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.FramesCommandName, "{}"); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.DialogCommandName, """{"action":"accept","promptText":"ok"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.DownloadsCommandName, """{"behavior":"allow","downloadPath":"/tmp/downloads","eventsEnabled":true}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.UploadCommandName, """{"selector":"#file","files":"[\"/tmp/file.txt\"]"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.BrowserUrlCommandName, "{}"); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.BackBrowserCommandName, "{}"); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.ForwardBrowserCommandName, "{}"); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.ReloadBrowserCommandName, "{}"); + var result = await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.ClickBrowserCommandName, """{"selector":"#submit","snapshotAfter":true}"""); + + Assert.True(result.Success); + Assert.NotNull(result.Data); + Assert.Equal(CommandResultFormat.Json, result.Data.Format); + Assert.True(result.Data.DisplayImmediately); + using (var resultDocument = JsonDocument.Parse(result.Data.Value)) + { + Assert.Equal("click", resultDocument.RootElement.GetProperty("action").GetString()); + Assert.True(resultDocument.RootElement.GetProperty("snapshotAfter").GetBoolean()); + Assert.Equal("snapshot", resultDocument.RootElement.GetProperty("snapshot").GetProperty("action").GetString()); + } + + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.DoubleClickBrowserCommandName, """{"selector":"#submit"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.CheckBrowserCommandName, """{"selector":"#accepted"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.UncheckBrowserCommandName, """{"selector":"#accepted"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.FocusBrowserElementCommandName, """{"selector":"#name"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.TypeBrowserTextCommandName, """{"selector":"#name","text":"Aspire"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.KeyDownBrowserCommandName, """{"selector":"#name","key":"Shift"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.KeyUpBrowserCommandName, """{"selector":"#name","key":"Shift"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.HoverBrowserElementCommandName, """{"selector":"#submit"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.ScrollBrowserCommandName, """{"selector":"#panel","deltaX":10,"deltaY":400}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.ScrollIntoViewBrowserCommandName, """{"selector":"#submit"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.MouseBrowserCommandName, """{"action":"click","x":10,"y":20,"button":"left"}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.WaitForBrowserUrlCommandName, """{"url":"/orders","match":"contains","timeoutMilliseconds":3000}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.WaitForBrowserLoadStateCommandName, """{"state":"networkidle","timeoutMilliseconds":3000}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.WaitForBrowserElementStateCommandName, """{"selector":"#submit","state":"enabled","timeoutMilliseconds":3000}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.WaitCommandName, """{"urlContains":"/dashboard","timeoutMilliseconds":3000}"""); + await ExecuteBrowserCommandAsync(BrowserAutomationBuilderExtensions.WaitCommandName, """{"function":"window.__appReady === true","timeoutMilliseconds":3000}"""); + + Assert.Equal( + [ + "GetAsync:web-browser-automation:attr:#link:href", + "IsAsync:web-browser-automation:visible:#submit", + "FindAsync:web-browser-automation:role:button:Submit:1", + "HighlightAsync:web-browser-automation:#submit", + "EvaluateAsync:web-browser-automation:document.title", + "CookiesAsync:web-browser-automation:set:session:abc::/", + "StorageAsync:web-browser-automation:local:set:theme:dark", + """StateAsync:web-browser-automation:set:{"cookies":[],"localStorage":{},"sessionStorage":{}}:True""", + """CdpAsync:web-browser-automation:Runtime.evaluate:{"expression":"document.title","returnByValue":true}:page""", + "TabsAsync:web-browser-automation:open:https://example.com/:", + "FramesAsync:web-browser-automation", + "DialogAsync:web-browser-automation:accept:ok", + "DownloadsAsync:web-browser-automation:allow:/tmp/downloads:True", + "UploadAsync:web-browser-automation:#file:[\"/tmp/file.txt\"]", + "GetUrlAsync:web-browser-automation", + "GoBackAsync:web-browser-automation", + "GoForwardAsync:web-browser-automation", + "ReloadAsync:web-browser-automation", + "ClickAsync:web-browser-automation:#submit", + "GetPageSnapshotAsync:web-browser-automation:80:8000", + "DoubleClickAsync:web-browser-automation:#submit", + "CheckAsync:web-browser-automation:#accepted", + "UncheckAsync:web-browser-automation:#accepted", + "FocusAsync:web-browser-automation:#name", + "TypeAsync:web-browser-automation:#name:Aspire", + "KeyDownAsync:web-browser-automation:#name:Shift", + "KeyUpAsync:web-browser-automation:#name:Shift", + "HoverAsync:web-browser-automation:#submit", + "ScrollAsync:web-browser-automation:#panel:10:400", + "ScrollIntoViewAsync:web-browser-automation:#submit", + "MouseAsync:web-browser-automation:click:10:20:left:0:0", + "WaitForUrlAsync:web-browser-automation:/orders:contains:3000", + "WaitForLoadStateAsync:web-browser-automation:networkidle:3000", + "WaitForElementStateAsync:web-browser-automation:#submit:enabled:3000", + "WaitForUrlAsync:web-browser-automation:/dashboard:contains:3000", + "WaitForFunctionAsync:web-browser-automation:window.__appReady === true:3000" + ], + sessionManager.BrowserCommandCalls); + + async Task ExecuteBrowserCommandAsync(string commandName, string json) + { + using var arguments = JsonDocument.Parse(json); + var commandResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, commandName, arguments.RootElement).DefaultTimeout(); + Assert.True(commandResult.Success); + return commandResult; + } + } + + [Fact] + public async Task WithBrowserAutomation_CaptureScreenshotCommandReturnsClearFailureWhenNoSessionIsActive() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); @@ -804,20 +1047,20 @@ public async Task WithBrowserLogs_CaptureScreenshotCommandReturnsClearFailureWhe Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.CaptureScreenshotCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.CaptureScreenshotCommandName).DefaultTimeout(); Assert.False(result.Success); Assert.Equal("No active tracked browser session is available to capture.", result.Message); } [Fact] - public async Task WithBrowserLogs_CaptureScreenshotCommandWritesPngArtifact() + public async Task WithBrowserAutomation_CaptureScreenshotCommandWritesPngArtifact() { var artifactDirectory = Directory.CreateTempSubdirectory(); try @@ -844,21 +1087,21 @@ public async Task WithBrowserLogs_CaptureScreenshotCommandWritesPngArtifact() Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var openResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var openResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(openResult.Success); var session = Assert.Single(sessionFactory.Sessions); session.ScreenshotBytes = [1, 2, 3, 4]; - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.CaptureScreenshotCommandName).DefaultTimeout(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.CaptureScreenshotCommandName).DefaultTimeout(); var logs = await ConsoleLoggingTestHelpers.WatchForLogsAsync( - app.Services.GetRequiredService().WatchAsync(browserLogsResource.Name), + app.Services.GetRequiredService().WatchAsync(browserAutomationResource.Name), targetLogCount: 6).DefaultTimeout(); Assert.True(result.Success); @@ -889,14 +1132,14 @@ public async Task WithBrowserLogs_CaptureScreenshotCommandWritesPngArtifact() } [Fact] - public async Task WithBrowserLogs_CommandUsesLatestConfiguredSettingsAndRefreshesProperties() + public async Task WithBrowserAutomation_CommandUsesLatestConfiguredSettingsAndRefreshesProperties() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory(); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"] = "Default"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = "Default"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.UserDataModeConfigurationKey}"] = nameof(BrowserUserDataMode.Shared); builder.Services.AddSingleton(sp => new BrowserLogsSessionManager( @@ -917,16 +1160,16 @@ public async Task WithBrowserLogs_CommandUsesLatestConfiguredSettingsAndRefreshe Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "msedge"; - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.ProfileConfigurationKey}"] = null; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "msedge"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.ProfileConfigurationKey}"] = null; - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(result.Success); @@ -935,11 +1178,11 @@ public async Task WithBrowserLogs_CommandUsesLatestConfiguredSettingsAndRefreshe Assert.Null(launchConfiguration.Profile); var runningEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserPropertyName, "msedge") && - !resourceEvent.Snapshot.Properties.Any(property => property.Name == BrowserLogsBuilderExtensions.ProfilePropertyName)).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserPropertyName, "msedge") && + !resourceEvent.Snapshot.Properties.Any(property => property.Name == BrowserAutomationBuilderExtensions.ProfilePropertyName)).DefaultTimeout(); var session = Assert.Single(GetBrowserSessions(runningEvent.Snapshot)); Assert.Equal("msedge", session.Browser); @@ -947,12 +1190,12 @@ public async Task WithBrowserLogs_CommandUsesLatestConfiguredSettingsAndRefreshe } [Fact] - public async Task WithBrowserLogs_CommandRefreshesBrowserExecutablePropertyWhenRelaunchFails() + public async Task WithBrowserAutomation_CommandRefreshesBrowserExecutablePropertyWhenRelaunchFails() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory(); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; builder.Services.AddSingleton(sp => new BrowserLogsSessionManager( @@ -973,21 +1216,21 @@ public async Task WithBrowserLogs_CommandRefreshesBrowserExecutablePropertyWhenR Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var firstResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var firstResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(firstResult.Success); await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-1")).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-1")).DefaultTimeout(); var tempDirectory = Directory.CreateTempSubdirectory(); @@ -996,25 +1239,25 @@ await app.ResourceNotifications.WaitForResourceAsync( var tempBrowserPath = Path.Combine(tempDirectory.FullName, OperatingSystem.IsWindows() ? "tracked-browser.exe" : "tracked-browser"); await File.WriteAllTextAsync(tempBrowserPath, string.Empty); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = tempBrowserPath; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = tempBrowserPath; sessionFactory.NextStartException = new InvalidOperationException("Launch failed."); - var secondResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var secondResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.False(secondResult.Success); Assert.Equal("Launch failed.", secondResult.Message); var failedEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserPropertyName, tempBrowserPath) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, tempBrowserPath) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserHostOwnershipPropertyName, nameof(BrowserHostOwnership.Owned)) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName, "InvalidOperationException: Launch failed.") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 1) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserPropertyName, tempBrowserPath) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, tempBrowserPath) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserHostOwnershipPropertyName, nameof(BrowserHostOwnership.Owned)) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName, "InvalidOperationException: Launch failed.") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 1) && resourceEvent.Snapshot.HealthReports.Any(report => - report.Name == BrowserLogsBuilderExtensions.LastErrorPropertyName && + report.Name == BrowserAutomationBuilderExtensions.LastErrorPropertyName && report.Status == HealthStatus.Unhealthy)).DefaultTimeout(); Assert.Collection( @@ -1032,12 +1275,12 @@ await app.ResourceNotifications.WaitForResourceAsync( } [Fact] - public async Task WithBrowserLogs_CommandRemovesStaleBrowserExecutablePropertyWhenBrowserCannotBeResolved() + public async Task WithBrowserAutomation_CommandRemovesStaleBrowserExecutablePropertyWhenBrowserCannotBeResolved() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory(); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "chrome"; builder.Services.AddSingleton(sp => new BrowserLogsSessionManager( @@ -1058,40 +1301,40 @@ public async Task WithBrowserLogs_CommandRemovesStaleBrowserExecutablePropertyWh Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var firstResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var firstResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(firstResult.Success); await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-1")).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-1")).DefaultTimeout(); - builder.Configuration[$"{BrowserLogsBuilderExtensions.BrowserLogsConfigurationSectionName}:{BrowserLogsBuilderExtensions.BrowserConfigurationKey}"] = "missing-browser"; + builder.Configuration[$"{BrowserAutomationBuilderExtensions.BrowserAutomationConfigurationSectionName}:{BrowserAutomationBuilderExtensions.BrowserConfigurationKey}"] = "missing-browser"; sessionFactory.NextStartException = new InvalidOperationException("Launch failed."); - var secondResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var secondResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.False(secondResult.Success); var failedEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserPropertyName, "missing-browser") && - !resourceEvent.Snapshot.Properties.Any(property => property.Name == BrowserLogsBuilderExtensions.BrowserExecutablePropertyName) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserHostOwnershipPropertyName, nameof(BrowserHostOwnership.Owned)) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName, "InvalidOperationException: Launch failed.") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 1) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserPropertyName, "missing-browser") && + !resourceEvent.Snapshot.Properties.Any(property => property.Name == BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserHostOwnershipPropertyName, nameof(BrowserHostOwnership.Owned)) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName, "InvalidOperationException: Launch failed.") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 1) && resourceEvent.Snapshot.HealthReports.Any(report => - report.Name == BrowserLogsBuilderExtensions.LastErrorPropertyName && + report.Name == BrowserAutomationBuilderExtensions.LastErrorPropertyName && report.Status == HealthStatus.Unhealthy)).DefaultTimeout(); Assert.Collection( @@ -1104,7 +1347,7 @@ await app.ResourceNotifications.WaitForResourceAsync( } [Fact] - public async Task WithBrowserLogs_CommandPublishesFailureDiagnosticsWhenLaunchFailsBeforeAnySession() + public async Task WithBrowserAutomation_CommandPublishesFailureDiagnosticsWhenLaunchFailsBeforeAnySession() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory @@ -1131,27 +1374,27 @@ public async Task WithBrowserLogs_CommandPublishesFailureDiagnosticsWhenLaunchFa Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.False(result.Success); Assert.Equal("Launch failed.", result.Message); var errorText = "InvalidOperationException: Launch failed. --> TimeoutException: CDP timed out."; var failedEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.FailedToStart && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 0) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, "None") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName, errorText) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 0) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, "None") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName, errorText) && resourceEvent.Snapshot.HealthReports.Any(report => - report.Name == BrowserLogsBuilderExtensions.LastErrorPropertyName && + report.Name == BrowserAutomationBuilderExtensions.LastErrorPropertyName && report.Status == HealthStatus.Unhealthy && report.Description == errorText)).DefaultTimeout(); @@ -1160,7 +1403,7 @@ public async Task WithBrowserLogs_CommandPublishesFailureDiagnosticsWhenLaunchFa } [Fact] - public async Task WithBrowserLogs_CommandClearsLastErrorAfterSuccessfulLaunch() + public async Task WithBrowserAutomation_CommandClearsLastErrorAfterSuccessfulLaunch() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory @@ -1187,31 +1430,31 @@ public async Task WithBrowserLogs_CommandClearsLastErrorAfterSuccessfulLaunch() Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var failedResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var failedResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.False(failedResult.Success); await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.FailedToStart && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName, "InvalidOperationException: Launch failed.")).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName, "InvalidOperationException: Launch failed.")).DefaultTimeout(); - var successfulResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var successfulResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(successfulResult.Success); var runningEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 1) && - DoesNotHaveProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName) && - !resourceEvent.Snapshot.HealthReports.Any(report => report.Name == BrowserLogsBuilderExtensions.LastErrorPropertyName)).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 1) && + DoesNotHaveProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName) && + !resourceEvent.Snapshot.HealthReports.Any(report => report.Name == BrowserAutomationBuilderExtensions.LastErrorPropertyName)).DefaultTimeout(); Assert.Collection( GetBrowserSessions(runningEvent.Snapshot), @@ -1219,7 +1462,7 @@ await app.ResourceNotifications.WaitForResourceAsync( } [Fact] - public async Task WithBrowserLogs_CommandSurfacesAdoptedBrowserDiagnostics() + public async Task WithBrowserAutomation_CommandSurfacesAdoptedBrowserDiagnostics() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory @@ -1247,21 +1490,21 @@ public async Task WithBrowserLogs_CommandSurfacesAdoptedBrowserDiagnostics() Properties = [] }); - web.WithBrowserLogs(browser: "msedge"); + web.WithBrowserAutomation(browser: "msedge"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(result.Success); var runningEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserHostOwnershipPropertyName, nameof(BrowserHostOwnership.Adopted)) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, "session-0001 (adopted browser)")).DefaultTimeout(); + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserHostOwnershipPropertyName, nameof(BrowserHostOwnership.Adopted)) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, "session-0001 (adopted browser)")).DefaultTimeout(); var session = Assert.Single(GetBrowserSessions(runningEvent.Snapshot)); Assert.Equal(nameof(BrowserHostOwnership.Adopted), session.BrowserHostOwnership); @@ -1269,7 +1512,7 @@ public async Task WithBrowserLogs_CommandSurfacesAdoptedBrowserDiagnostics() } [Fact] - public async Task WithBrowserLogs_CommandFailsWhenEndpointIsMissing() + public async Task WithBrowserAutomation_CommandFailsWhenEndpointIsMissing() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionManager = new FakeBrowserLogsSessionManager(); @@ -1283,21 +1526,21 @@ public async Task WithBrowserLogs_CommandFailsWhenEndpointIsMissing() Properties = [] }); - web.WithBrowserLogs(); + web.WithBrowserAutomation(); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.False(result.Success); - Assert.Equal("Resource 'web' does not have an HTTP or HTTPS endpoint. Browser logs require an endpoint to navigate to.", result.Message); + Assert.Equal("Resource 'web' does not have an HTTP or HTTPS endpoint. Browser automation requires an endpoint to navigate to.", result.Message); Assert.Empty(sessionManager.Calls); } [Fact] - public async Task WithBrowserLogs_CommandBecomesEnabledWhenParentReady() + public async Task WithBrowserAutomation_CommandBecomesEnabledWhenParentReady() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); @@ -1311,19 +1554,19 @@ public async Task WithBrowserLogs_CommandBecomesEnabledWhenParentReady() Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); var initialEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.Commands.Any(command => - command.Name == BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName && + command.Name == BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName && command.State == ResourceCommandState.Disabled)).DefaultTimeout(); - Assert.Equal(ResourceCommandState.Disabled, initialEvent.Snapshot.Commands.Single(command => command.Name == BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).State); + Assert.Equal(ResourceCommandState.Disabled, initialEvent.Snapshot.Commands.Single(command => command.Name == BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).State); await app.ResourceNotifications.PublishUpdateAsync(web.Resource, snapshot => snapshot with { @@ -1334,16 +1577,16 @@ await app.ResourceNotifications.PublishUpdateAsync(web.Resource, snapshot => sna await eventing.PublishAsync(new ResourceReadyEvent(web.Resource, app.Services)).DefaultTimeout(); var enabledEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.Commands.Any(command => - command.Name == BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName && + command.Name == BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName && command.State == ResourceCommandState.Enabled)).DefaultTimeout(); - Assert.Equal(ResourceCommandState.Enabled, enabledEvent.Snapshot.Commands.Single(command => command.Name == BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).State); + Assert.Equal(ResourceCommandState.Enabled, enabledEvent.Snapshot.Commands.Single(command => command.Name == BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).State); } [Fact] - public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() + public async Task WithBrowserAutomation_CommandTracksMultipleSessionsWithUniqueIds() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory(); @@ -1367,28 +1610,28 @@ public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() Properties = [] }); - web.WithBrowserLogs(browser: "chrome", profile: "Default", userDataMode: BrowserUserDataMode.Shared); + web.WithBrowserAutomation(browser: "chrome", profile: "Default", userDataMode: BrowserUserDataMode.Shared); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var firstResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var firstResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(firstResult.Success); var firstSession = Assert.Single(sessionFactory.Sessions); Assert.Equal("session-0001", firstSession.SessionId); var firstRunningEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 1) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, "session-0001 (PID 1001)") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.TotalSessionsLaunchedPropertyName, 1) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastSessionPropertyName, "session-0001") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-1") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 1) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, "session-0001 (PID 1001)") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.TotalSessionsLaunchedPropertyName, 1) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastSessionPropertyName, "session-0001") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-1") && resourceEvent.Snapshot.HealthReports.Any(report => report.Name == "session-0001" && report.Status == HealthStatus.Healthy)).DefaultTimeout(); Assert.Single(firstRunningEvent.Snapshot.HealthReports); @@ -1409,7 +1652,7 @@ public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() }); Assert.Equal(0, firstSession.StopCallCount); - var secondResult = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var secondResult = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(secondResult.Success); Assert.Equal(2, sessionFactory.Sessions.Count); @@ -1417,14 +1660,14 @@ public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() Assert.Equal("session-0002", secondSession.SessionId); var secondRunningEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 2) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, "session-0001 (PID 1001), session-0002 (PID 1002)") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.TotalSessionsLaunchedPropertyName, 2) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastSessionPropertyName, "session-0002") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-2") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 2) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, "session-0001 (PID 1001), session-0002 (PID 1002)") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.TotalSessionsLaunchedPropertyName, 2) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastSessionPropertyName, "session-0002") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.BrowserExecutablePropertyName, "/fake/browser-2") && resourceEvent.Snapshot.HealthReports.Any(report => report.Name == "session-0001" && report.Status == HealthStatus.Healthy) && resourceEvent.Snapshot.HealthReports.Any(report => report.Name == "session-0002" && report.Status == HealthStatus.Healthy)).DefaultTimeout(); @@ -1449,11 +1692,11 @@ public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() await firstSession.CompleteAsync(exitCode: 0); var firstCompletedEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 1) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, "session-0002 (PID 1002)") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 1) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, "session-0002 (PID 1002)") && resourceEvent.Snapshot.HealthReports.Length == 1 && resourceEvent.Snapshot.HealthReports[0].Name == "session-0002").DefaultTimeout(); @@ -1465,12 +1708,12 @@ public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() await secondSession.CompleteAsync(exitCode: 0); var allCompletedEvent = await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Finished && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 0) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionsPropertyName, "None") && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.TotalSessionsLaunchedPropertyName, 2) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 0) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionsPropertyName, "None") && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.TotalSessionsLaunchedPropertyName, 2) && resourceEvent.Snapshot.HealthReports.IsEmpty).DefaultTimeout(); Assert.Equal(KnownResourceStates.Finished, allCompletedEvent.Snapshot.State?.Text); @@ -1478,7 +1721,7 @@ public async Task WithBrowserLogs_CommandTracksMultipleSessionsWithUniqueIds() } [Fact] - public async Task WithBrowserLogs_PreservesLastErrorWhenOneOfMultipleSessionsFails() + public async Task WithBrowserAutomation_PreservesLastErrorWhenOneOfMultipleSessionsFails() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory(); @@ -1502,15 +1745,15 @@ public async Task WithBrowserLogs_PreservesLastErrorWhenOneOfMultipleSessionsFai Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); using var app = builder.Build(); await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); - Assert.True((await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout()).Success); - Assert.True((await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout()).Success); + Assert.True((await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout()).Success); + Assert.True((await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout()).Success); var firstSession = sessionFactory.Sessions[0]; var secondSession = sessionFactory.Sessions[1]; @@ -1518,35 +1761,35 @@ public async Task WithBrowserLogs_PreservesLastErrorWhenOneOfMultipleSessionsFai var errorText = "InvalidOperationException: Target crashed."; await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Running && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 1) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName, errorText) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 1) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName, errorText) && resourceEvent.Snapshot.HealthReports.Any(report => report.Name == "session-0002" && report.Status == HealthStatus.Healthy) && resourceEvent.Snapshot.HealthReports.Any(report => - report.Name == BrowserLogsBuilderExtensions.LastErrorPropertyName && + report.Name == BrowserAutomationBuilderExtensions.LastErrorPropertyName && report.Status == HealthStatus.Unhealthy && report.Description == errorText)).DefaultTimeout(); await secondSession.CompleteAsync(exitCode: 0); await app.ResourceNotifications.WaitForResourceAsync( - browserLogsResource.Name, + browserAutomationResource.Name, resourceEvent => resourceEvent.Snapshot.State?.Text == KnownResourceStates.Exited && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.ActiveSessionCountPropertyName, 0) && - HasProperty(resourceEvent.Snapshot, BrowserLogsBuilderExtensions.LastErrorPropertyName, errorText) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.ActiveSessionCountPropertyName, 0) && + HasProperty(resourceEvent.Snapshot, BrowserAutomationBuilderExtensions.LastErrorPropertyName, errorText) && resourceEvent.Snapshot.HealthReports.Any(report => - report.Name == BrowserLogsBuilderExtensions.LastErrorPropertyName && + report.Name == BrowserAutomationBuilderExtensions.LastErrorPropertyName && report.Status == HealthStatus.Unhealthy && report.Description == errorText)).DefaultTimeout(); } [Fact] - public async Task WithBrowserLogs_DisposeWaitsForCompletionObservers() + public async Task WithBrowserAutomation_DisposeWaitsForCompletionObservers() { using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); var sessionFactory = new FakeBrowserLogsRunningSessionFactory(); @@ -1570,7 +1813,7 @@ public async Task WithBrowserLogs_DisposeWaitsForCompletionObservers() Properties = [] }); - web.WithBrowserLogs(browser: "chrome"); + web.WithBrowserAutomation(browser: "chrome"); var app = builder.Build(); var disposed = false; @@ -1579,8 +1822,8 @@ public async Task WithBrowserLogs_DisposeWaitsForCompletionObservers() { await app.StartAsync(); - var browserLogsResource = app.Services.GetRequiredService().Resources.OfType().Single(); - var result = await app.ResourceCommands.ExecuteCommandAsync(browserLogsResource, BrowserLogsBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); + var browserAutomationResource = app.Services.GetRequiredService().Resources.OfType().Single(); + var result = await app.ResourceCommands.ExecuteCommandAsync(browserAutomationResource, BrowserAutomationBuilderExtensions.OpenTrackedBrowserCommandName).DefaultTimeout(); Assert.True(result.Success); var session = Assert.Single(sessionFactory.Sessions); @@ -1608,9 +1851,9 @@ public async Task WithBrowserLogs_DisposeWaitsForCompletionObservers() public async Task BrowserEventLogger_LogsSuccessfulNetworkRequests() { var resourceLoggerService = ConsoleLoggingTestHelpers.GetResourceLoggerService(); - var resourceLogger = resourceLoggerService.GetLogger("web-browser-logs"); + var resourceLogger = resourceLoggerService.GetLogger("web-browser-automation"); var eventLogger = new BrowserEventLogger("session-0001", resourceLogger); - var logs = await CaptureLogsAsync(resourceLoggerService, "web-browser-logs", () => + var logs = await CaptureLogsAsync(resourceLoggerService, "web-browser-automation", () => { eventLogger.HandleEvent(ParseProtocolEvent(""" { @@ -1666,9 +1909,9 @@ public async Task BrowserEventLogger_LogsSuccessfulNetworkRequests() public async Task BrowserEventLogger_LogsFailedNetworkRequests() { var resourceLoggerService = ConsoleLoggingTestHelpers.GetResourceLoggerService(); - var resourceLogger = resourceLoggerService.GetLogger("web-browser-logs"); + var resourceLogger = resourceLoggerService.GetLogger("web-browser-automation"); var eventLogger = new BrowserEventLogger("session-0002", resourceLogger); - var logs = await CaptureLogsAsync(resourceLoggerService, "web-browser-logs", () => + var logs = await CaptureLogsAsync(resourceLoggerService, "web-browser-automation", () => { eventLogger.HandleEvent(ParseProtocolEvent(""" { @@ -1713,7 +1956,7 @@ private static bool DoesNotHaveProperty(CustomResourceSnapshot snapshot, string private static IReadOnlyList GetBrowserSessions(CustomResourceSnapshot snapshot) { - var property = snapshot.Properties.Single(property => property.Name == BrowserLogsBuilderExtensions.BrowserSessionsPropertyName); + var property = snapshot.Properties.Single(property => property.Name == BrowserAutomationBuilderExtensions.BrowserSessionsPropertyName); var value = Assert.IsType(property.Value); return JsonSerializer.Deserialize>(value, BrowserSessionPropertyJsonOptions) ?? throw new InvalidOperationException("Expected browser session property JSON."); @@ -1735,6 +1978,10 @@ private sealed class FakeBrowserLogsSessionManager : IBrowserLogsSessionManager public List CaptureScreenshotCalls { get; } = []; + public List BrowserCommandCalls { get; } = []; + + public BrowserScreenshotCaptureOptions? ScreenshotOptions { get; private set; } + public BrowserLogsScreenshotCaptureResult ScreenshotResult { get; set; } = new( "session-0001", "chrome", @@ -1744,27 +1991,274 @@ private sealed class FakeBrowserLogsSessionManager : IBrowserLogsSessionManager "target-1", new Uri("https://localhost:5001/"), new BrowserLogsArtifact( - "web-browser-logs", + "web-browser-automation", "screenshot", Path.Combine(AppContext.BaseDirectory, "screenshot.png"), "image/png", 0, DateTimeOffset.UnixEpoch)); - public Task StartSessionAsync(BrowserLogsResource resource, BrowserConfiguration configuration, string resourceName, Uri url, CancellationToken cancellationToken) + public Task StartSessionAsync(BrowserAutomationResource resource, BrowserConfiguration configuration, string resourceName, Uri url, CancellationToken cancellationToken) { Calls.Add(new SessionStartCall(resource, configuration, resourceName, url)); return Task.CompletedTask; } - public Task CaptureScreenshotAsync(string resourceName, CancellationToken cancellationToken) + public Task CaptureScreenshotAsync(string resourceName, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { CaptureScreenshotCalls.Add(resourceName); + ScreenshotOptions = options; return Task.FromResult(ScreenshotResult); } + + public Task GetPageSnapshotAsync(string resourceName, int maxElements, int maxTextLength, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(GetPageSnapshotAsync)}:{resourceName}:{maxElements}:{maxTextLength}"); + return Task.FromResult("""{"action":"snapshot"}"""); + } + + public Task GetAsync(string resourceName, string property, string? selector, string? name, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(GetAsync)}:{resourceName}:{property}:{selector}:{name}"); + return Task.FromResult("""{"action":"get"}"""); + } + + public Task IsAsync(string resourceName, string state, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(IsAsync)}:{resourceName}:{state}:{selector}"); + return Task.FromResult("""{"action":"is"}"""); + } + + public Task FindAsync(string resourceName, string kind, string value, string? name, int index, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(FindAsync)}:{resourceName}:{kind}:{value}:{name}:{index}"); + return Task.FromResult("""{"action":"find"}"""); + } + + public Task HighlightAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(HighlightAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"highlight"}"""); + } + + public Task EvaluateAsync(string resourceName, string expression, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(EvaluateAsync)}:{resourceName}:{expression}"); + return Task.FromResult("""{"action":"eval"}"""); + } + + public Task CookiesAsync(string resourceName, string action, string? name, string? value, string? domain, string? path, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(CookiesAsync)}:{resourceName}:{action}:{name}:{value}:{domain}:{path}"); + return Task.FromResult("""{"action":"cookies"}"""); + } + + public Task StorageAsync(string resourceName, string area, string action, string? key, string? value, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(StorageAsync)}:{resourceName}:{area}:{action}:{key}:{value}"); + return Task.FromResult("""{"action":"storage"}"""); + } + + public Task StateAsync(string resourceName, string action, string? state, bool clearExisting, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(StateAsync)}:{resourceName}:{action}:{state}:{clearExisting}"); + return Task.FromResult("""{"action":"state"}"""); + } + + public Task CdpAsync(string resourceName, string method, string? parametersJson, string session, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(CdpAsync)}:{resourceName}:{method}:{parametersJson}:{session}"); + return Task.FromResult("""{"action":"cdp"}"""); + } + + public Task TabsAsync(string resourceName, string action, string? url, string? targetId, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(TabsAsync)}:{resourceName}:{action}:{url}:{targetId}"); + return Task.FromResult("""{"action":"tabs"}"""); + } + + public Task FramesAsync(string resourceName, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(FramesAsync)}:{resourceName}"); + return Task.FromResult("""{"action":"frames"}"""); + } + + public Task DialogAsync(string resourceName, string action, string? promptText, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(DialogAsync)}:{resourceName}:{action}:{promptText}"); + return Task.FromResult("""{"action":"dialog"}"""); + } + + public Task DownloadsAsync(string resourceName, string behavior, string? downloadPath, bool eventsEnabled, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(DownloadsAsync)}:{resourceName}:{behavior}:{downloadPath}:{eventsEnabled}"); + return Task.FromResult("""{"action":"downloads"}"""); + } + + public Task UploadAsync(string resourceName, string selector, string files, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(UploadAsync)}:{resourceName}:{selector}:{files}"); + return Task.FromResult("""{"action":"upload"}"""); + } + + public Task GetUrlAsync(string resourceName, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(GetUrlAsync)}:{resourceName}"); + return Task.FromResult("""{"action":"url"}"""); + } + + public Task GoBackAsync(string resourceName, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(GoBackAsync)}:{resourceName}"); + return Task.FromResult("""{"action":"back"}"""); + } + + public Task GoForwardAsync(string resourceName, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(GoForwardAsync)}:{resourceName}"); + return Task.FromResult("""{"action":"forward"}"""); + } + + public Task ReloadAsync(string resourceName, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(ReloadAsync)}:{resourceName}"); + return Task.FromResult("""{"action":"reload"}"""); + } + + public Task NavigateAsync(BrowserAutomationResource resource, string resourceName, Uri url, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(NavigateAsync)}:{resourceName}:{url}"); + return Task.FromResult("""{"action":"navigate"}"""); + } + + public Task ClickAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(ClickAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"click"}"""); + } + + public Task DoubleClickAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(DoubleClickAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"dblclick"}"""); + } + + public Task FillAsync(string resourceName, string selector, string value, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(FillAsync)}:{resourceName}:{selector}:{value}"); + return Task.FromResult("""{"action":"fill"}"""); + } + + public Task CheckAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(CheckAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"check"}"""); + } + + public Task UncheckAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(UncheckAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"uncheck"}"""); + } + + public Task FocusAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(FocusAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"focus"}"""); + } + + public Task TypeAsync(string resourceName, string selector, string text, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(TypeAsync)}:{resourceName}:{selector}:{text}"); + return Task.FromResult("""{"action":"type"}"""); + } + + public Task PressAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(PressAsync)}:{resourceName}:{selector}:{key}"); + return Task.FromResult("""{"action":"press"}"""); + } + + public Task KeyDownAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(KeyDownAsync)}:{resourceName}:{selector}:{key}"); + return Task.FromResult("""{"action":"keydown"}"""); + } + + public Task KeyUpAsync(string resourceName, string? selector, string key, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(KeyUpAsync)}:{resourceName}:{selector}:{key}"); + return Task.FromResult("""{"action":"keyup"}"""); + } + + public Task HoverAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(HoverAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"hover"}"""); + } + + public Task SelectAsync(string resourceName, string selector, string value, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(SelectAsync)}:{resourceName}:{selector}:{value}"); + return Task.FromResult("""{"action":"select"}"""); + } + + public Task ScrollAsync(string resourceName, string? selector, int deltaX, int deltaY, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(ScrollAsync)}:{resourceName}:{selector}:{deltaX}:{deltaY}"); + return Task.FromResult("""{"action":"scroll"}"""); + } + + public Task ScrollIntoViewAsync(string resourceName, string selector, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(ScrollIntoViewAsync)}:{resourceName}:{selector}"); + return Task.FromResult("""{"action":"scroll-into-view"}"""); + } + + public Task MouseAsync(string resourceName, string action, int x, int y, string? button, int deltaX, int deltaY, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(MouseAsync)}:{resourceName}:{action}:{x}:{y}:{button}:{deltaX}:{deltaY}"); + return Task.FromResult("""{"action":"mouse"}"""); + } + + public Task WaitForAsync(string resourceName, string? selector, string? text, int timeoutMilliseconds, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(WaitForAsync)}:{resourceName}:{selector}:{text}:{timeoutMilliseconds}"); + return Task.FromResult("""{"action":"wait-for"}"""); + } + + public Task WaitForUrlAsync(string resourceName, string url, string match, int timeoutMilliseconds, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(WaitForUrlAsync)}:{resourceName}:{url}:{match}:{timeoutMilliseconds}"); + return Task.FromResult("""{"action":"wait-for-url"}"""); + } + + public Task WaitForLoadStateAsync(string resourceName, string state, int timeoutMilliseconds, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(WaitForLoadStateAsync)}:{resourceName}:{state}:{timeoutMilliseconds}"); + return Task.FromResult("""{"action":"wait-for-load-state"}"""); + } + + public Task WaitForElementStateAsync(string resourceName, string selector, string state, int timeoutMilliseconds, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(WaitForElementStateAsync)}:{resourceName}:{selector}:{state}:{timeoutMilliseconds}"); + return Task.FromResult("""{"action":"wait-for-element-state"}"""); + } + + public Task WaitForFunctionAsync(string resourceName, string function, int timeoutMilliseconds, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(WaitForFunctionAsync)}:{resourceName}:{function}:{timeoutMilliseconds}"); + return Task.FromResult("""{"action":"wait-for-function"}"""); + } + + public Task CloseActiveSessionAsync(string resourceName, CancellationToken cancellationToken) + { + BrowserCommandCalls.Add($"{nameof(CloseActiveSessionAsync)}:{resourceName}"); + return Task.FromResult("""{"action":"close"}"""); + } } - private sealed record SessionStartCall(BrowserLogsResource Resource, BrowserConfiguration Configuration, string ResourceName, Uri Url); + private sealed record SessionStartCall(BrowserAutomationResource Resource, BrowserConfiguration Configuration, string ResourceName, Uri Url); private sealed class FakeBrowserLogsRunningSessionFactory : IBrowserLogsRunningSessionFactory { @@ -1857,11 +2351,26 @@ public Task StopAsync(CancellationToken cancellationToken) return Task.CompletedTask; } - public Task CaptureScreenshotAsync(CancellationToken cancellationToken) + public Task CaptureScreenshotAsync(BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { return Task.FromResult(ScreenshotBytes); } + public Task NavigateAsync(Uri url, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task EvaluateJsonAsync(string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + return Task.FromResult("""{"action":"evaluate"}"""); + } + + public Task SendCdpCommandJsonAsync(string method, string? parametersJson, string session, CancellationToken cancellationToken) + { + return Task.FromResult("""{"action":"cdp"}"""); + } + public async Task CompleteAsync(int exitCode, Exception? error = null) { _completionSource.TrySetResult((exitCode, error)); @@ -1959,4 +2468,4 @@ private sealed record BrowserSessionPropertyValue( #pragma warning restore ASPIREUSERSECRETS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. #pragma warning restore ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. -#pragma warning restore ASPIREBROWSERLOGS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +#pragma warning restore ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserConnectionDiagnosticsLoggerTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserConnectionDiagnosticsLoggerTests.cs index 4a58d26bd6c..2c13d250545 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserConnectionDiagnosticsLoggerTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserConnectionDiagnosticsLoggerTests.cs @@ -13,7 +13,7 @@ public class BrowserConnectionDiagnosticsLoggerTests public async Task LogsConnectionProblems() { var resourceLoggerService = ConsoleLoggingTestHelpers.GetResourceLoggerService(); - var resourceName = "web-browser-logs"; + var resourceName = "web-browser-automation"; var diagnostics = new BrowserConnectionDiagnosticsLogger("session-0001", resourceLoggerService.GetLogger(resourceName)); var logs = await ConsoleLoggingTestHelpers.CaptureLogsAsync(resourceLoggerService, resourceName, targetLogCount: 4, () => diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpConnectionTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpConnectionTests.cs index 668fe4373e4..4c2d6db03f5 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpConnectionTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpConnectionTests.cs @@ -121,13 +121,15 @@ public async Task CaptureScreenshotAsync_SendsPageCaptureScreenshotForTargetSess CancellationToken.None, () => connector); - var captureTask = connection.CaptureScreenshotAsync("target-session-1", CancellationToken.None); + var captureTask = connection.CaptureScreenshotAsync("target-session-1", new BrowserScreenshotCaptureOptions("jpeg", 80, FullPage: true), CancellationToken.None); var command = await ReceiveCommandAsync(pair.ServerSocket).DefaultTimeout(); Assert.Equal(BrowserLogsCdpProtocol.PageCaptureScreenshotMethod, command.Method); Assert.Equal("target-session-1", command.SessionId); - Assert.Equal("png", command.Format); + Assert.Equal("jpeg", command.Format); Assert.Equal(true, command.FromSurface); + Assert.Equal(80, command.Quality); + Assert.Equal(true, command.CaptureBeyondViewport); await SendTextAsync( pair.ServerSocket, @@ -146,6 +148,49 @@ await SendTextAsync( await pair.ServerSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None).DefaultTimeout(); } + [Fact] + public async Task EvaluateAsync_SendsRuntimeEvaluateForTargetSession() + { + await using var pair = InMemoryWebSocketPair.Create(); + var connector = new ConnectedClientWebSocketConnector(pair.ClientSocket); + await using var connection = await BrowserLogsCdpConnection.ConnectAsync( + new Uri("ws://127.0.0.1/devtools/browser/test"), + static _ => ValueTask.CompletedTask, + NullLogger.Instance, + CancellationToken.None, + () => connector); + + var evaluateTask = connection.EvaluateAsync("target-session-1", "1 + 1", timeout: null, CancellationToken.None); + + var command = await ReceiveCommandAsync(pair.ServerSocket).DefaultTimeout(); + Assert.Equal(BrowserLogsCdpProtocol.RuntimeEvaluateMethod, command.Method); + Assert.Equal("target-session-1", command.SessionId); + Assert.Equal("1 + 1", command.Expression); + Assert.True(command.AwaitPromise); + Assert.True(command.ReturnByValue); + Assert.True(command.UserGesture); + + await SendTextAsync( + pair.ServerSocket, + $$""" + { + "id": {{command.Id}}, + "result": { + "result": { + "type": "string", + "value": "{\"value\":2}" + } + } + } + """).DefaultTimeout(); + + var result = await evaluateTask.DefaultTimeout(); + var value = Assert.IsType(result.Result?.Value); + Assert.Equal("""{"value":2}""", value.Value); + + await pair.ServerSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None).DefaultTimeout(); + } + [Fact] public async Task CreateWithPipeTransport_UsesNullDelimitedFrames() { @@ -327,8 +372,26 @@ private static ReceivedCommand ParseReceivedCommand(JsonElement root) var fromSurface = parameters?.TryGetProperty("fromSurface", out var fromSurfaceElement) == true ? fromSurfaceElement.GetBoolean() : (bool?)null; + var quality = parameters?.TryGetProperty("quality", out var qualityElement) == true + ? qualityElement.GetInt32() + : (int?)null; + var captureBeyondViewport = parameters?.TryGetProperty("captureBeyondViewport", out var captureBeyondViewportElement) == true + ? captureBeyondViewportElement.GetBoolean() + : (bool?)null; + var expression = parameters?.TryGetProperty("expression", out var expressionElement) == true + ? expressionElement.GetString() + : null; + var awaitPromise = parameters?.TryGetProperty("awaitPromise", out var awaitPromiseElement) == true + ? awaitPromiseElement.GetBoolean() + : (bool?)null; + var returnByValue = parameters?.TryGetProperty("returnByValue", out var returnByValueElement) == true + ? returnByValueElement.GetBoolean() + : (bool?)null; + var userGesture = parameters?.TryGetProperty("userGesture", out var userGestureElement) == true + ? userGestureElement.GetBoolean() + : (bool?)null; - return new ReceivedCommand(id, method, sessionId, targetId, url, format, fromSurface); + return new ReceivedCommand(id, method, sessionId, targetId, url, format, fromSurface, quality, captureBeyondViewport, expression, awaitPromise, returnByValue, userGesture); } private static BrowserLogsConsoleApiCalledEvent CreateConsoleEvent(string sessionId) @@ -401,7 +464,20 @@ private static async Task SendNullTerminatedFramesAsync(Stream stream, params st await stream.FlushAsync(); } - private sealed record ReceivedCommand(long Id, string Method, string? SessionId, string? TargetId, string? Url, string? Format, bool? FromSurface); + private sealed record ReceivedCommand( + long Id, + string Method, + string? SessionId, + string? TargetId, + string? Url, + string? Format, + bool? FromSurface, + int? Quality, + bool? CaptureBeyondViewport, + string? Expression, + bool? AwaitPromise, + bool? ReturnByValue, + bool? UserGesture); private sealed class FakeSharedCdpConnection(Func eventHandler) : IBrowserLogsCdpConnection { @@ -454,7 +530,7 @@ public Task EnablePageInstrumentationAsync(string sessionId, CancellationToken c return Task.CompletedTask; } - public Task CaptureScreenshotAsync(string sessionId, CancellationToken cancellationToken) + public Task CaptureScreenshotAsync(string sessionId, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { return Task.FromResult(new BrowserLogsCaptureScreenshotResult { Data = "image-data" }); } @@ -464,6 +540,22 @@ public Task NavigateAsync(string sessionId, Uri url, Canc return Task.FromResult(BrowserLogsCommandAck.Instance); } + public Task EvaluateAsync(string sessionId, string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + return Task.FromResult(new BrowserLogsRuntimeEvaluateResult + { + Result = new BrowserLogsCdpProtocolRemoteObject + { + Value = new BrowserLogsCdpProtocolStringValue("""{"action":"evaluate"}""") + } + }); + } + + public Task SendRawCommandAsync(string? sessionId, string method, string? parametersJson, CancellationToken cancellationToken) + { + return Task.FromResult("""{"ok":true}"""); + } + public ValueTask DisposeAsync() { Disposed = true; diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpProtocolTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpProtocolTests.cs index 39efb4059e4..a009ccae988 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpProtocolTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsCdpProtocolTests.cs @@ -95,6 +95,91 @@ public void ParseCaptureScreenshotResponse_ReturnsBase64ImageData() Assert.Equal("aW1hZ2UtZGF0YQ==", result.Data); } + [Fact] + public void ParseRuntimeEvaluateResponse_ReturnsRemoteObject() + { + var payload = Encoding.UTF8.GetBytes(""" + { + "id": 9, + "result": { + "result": { + "type": "string", + "value": "{\"action\":\"snapshot\"}" + } + } + } + """); + + var result = BrowserLogsCdpProtocol.ParseRuntimeEvaluateResponse(payload); + var value = Assert.IsType(result.Result?.Value); + + Assert.Equal("""{"action":"snapshot"}""", value.Value); + } + + [Fact] + public void ParseRuntimeEvaluateResponse_ThrowsForExceptionDetails() + { + var payload = Encoding.UTF8.GetBytes(""" + { + "id": 9, + "result": { + "result": { + "type": "object" + }, + "exceptionDetails": { + "text": "Uncaught", + "exception": { + "description": "Error: boom" + } + } + } + } + """); + + var exception = Assert.Throws(() => BrowserLogsCdpProtocol.ParseRuntimeEvaluateResponse(payload)); + + Assert.Equal("Error: boom", exception.Message); + } + + [Fact] + public void ParseRawCommandResponse_ReturnsResultJson() + { + var payload = Encoding.UTF8.GetBytes(""" + { + "id": 9, + "result": { + "value": 42, + "nested": { + "ok": true + } + } + } + """); + + var result = BrowserLogsCdpProtocol.ParseRawCommandResponse(payload); + + Assert.Equal("""{"value":42,"nested":{"ok":true}}""", result); + } + + [Fact] + public void ParseRawCommandResponse_ThrowsForProtocolError() + { + var payload = Encoding.UTF8.GetBytes(""" + { + "id": 9, + "error": { + "code": -32601, + "message": "Method not found" + } + } + """); + + var exception = Assert.Throws(() => BrowserLogsCdpProtocol.ParseRawCommandResponse(payload)); + + Assert.Contains("Method not found", exception.Message); + Assert.Contains("-32601", exception.Message); + } + [Fact] public void ParseEvent_TargetDetachedFromTarget_UsesParameterSessionId() { diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsRunningSessionTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsRunningSessionTests.cs index 2b0c17c8570..d79874d011d 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsRunningSessionTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsRunningSessionTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only using Aspire.Hosting.Tests.Utils; using Microsoft.AspNetCore.InternalTesting; @@ -36,7 +36,7 @@ public async Task RunningSessionRoutesPageEventsToResourceLogsAndReleasesHostOnC }); var resourceLoggerService = ConsoleLoggingTestHelpers.GetResourceLoggerService(); - var resourceName = "web-browser-logs"; + var resourceName = "web-browser-automation"; BrowserLogsRunningSession? session = null; var logs = await ConsoleLoggingTestHelpers.CaptureLogsAsync(resourceLoggerService, resourceName, targetLogCount: 5, () => { @@ -164,11 +164,26 @@ private sealed class TestBrowserPageSession( public int DisposeCount { get; private set; } - public Task CaptureScreenshotAsync(CancellationToken cancellationToken) + public Task CaptureScreenshotAsync(BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { return Task.FromResult(new BrowserLogsCaptureScreenshotResult { Data = Convert.ToBase64String([0x89, 0x50, 0x4e, 0x47]) }); } + public Task NavigateAsync(Uri url, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task EvaluateJsonAsync(string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + return Task.FromResult("""{"action":"evaluate"}"""); + } + + public Task SendCdpCommandJsonAsync(string method, string? parametersJson, string session, CancellationToken cancellationToken) + { + return Task.FromResult("""{"action":"cdp"}"""); + } + public ValueTask RaiseEventAsync(BrowserLogsCdpProtocolEvent protocolEvent) { return eventHandler(protocolEvent); diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsSessionManagerTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsSessionManagerTests.cs index 44047c23ede..fe6e33d92f8 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsSessionManagerTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserLogsSessionManagerTests.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions; #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only namespace Aspire.Hosting.Browsers.Tests; @@ -541,8 +541,8 @@ public async Task StartSessionAsync_ThrowsWhenManagerIsDisposing() NullLogger.Instance, artifactWriter: null, sessionFactory: sessionFactory); - var resource = new BrowserLogsResource( - "web-browser-logs", + var resource = new BrowserAutomationResource( + "web-browser-automation", new TestResourceWithEndpoints("web"), new BrowserConfiguration("chrome", null, BrowserUserDataMode.Isolated, AppHostKey: "test-apphost"), new BrowserConfigurationExplicitValues()); diff --git a/tests/Aspire.Hosting.Browsers.Tests/BrowserPageSessionTests.cs b/tests/Aspire.Hosting.Browsers.Tests/BrowserPageSessionTests.cs index 5f534c369a7..85bc5654f9d 100644 --- a/tests/Aspire.Hosting.Browsers.Tests/BrowserPageSessionTests.cs +++ b/tests/Aspire.Hosting.Browsers.Tests/BrowserPageSessionTests.cs @@ -98,6 +98,39 @@ await connection.RaiseEventAsync(new BrowserLogsTargetDestroyedEvent( await session.DisposeAsync(); } + [Fact] + public async Task SendCdpCommandJsonAsync_UsesPageOrBrowserSession() + { + var host = new TestBrowserHost(); + var connection = new FakeBrowserLogsCdpConnection + { + CreatedTargetId = "created-target", + AttachSessionId = "target-session-1" + }; + + var session = await BrowserPageSession.StartAsync( + host, + "session-0001", + new Uri("https://localhost:5001/"), + new BrowserConnectionDiagnosticsLogger("session-0001", NullLogger.Instance), + CreateConnectionFactory(connection), + static _ => ValueTask.CompletedTask, + NullLogger.Instance, + TimeProvider.System, + reuseInitialBlankTarget: false, + CancellationToken.None); + + var pageResult = await session.SendCdpCommandJsonAsync("Runtime.evaluate", """{"expression":"document.title"}""", "page", CancellationToken.None); + var browserResult = await session.SendCdpCommandJsonAsync("Target.getTargets", parametersJson: null, "browser", CancellationToken.None); + + Assert.Equal("""{"ok":true}""", pageResult); + Assert.Equal("""{"ok":true}""", browserResult); + Assert.Contains("Raw:target-session-1:Runtime.evaluate:{\"expression\":\"document.title\"}", connection.Calls); + Assert.Contains("Raw::Target.getTargets:", connection.Calls); + + await session.DisposeAsync(); + } + [Fact] public async Task DisposeAsync_ClosesTrackedTarget() { @@ -159,7 +192,7 @@ public async Task CaptureScreenshotAsync_UsesCurrentTargetSession() reuseInitialBlankTarget: false, CancellationToken.None); - var result = await session.CaptureScreenshotAsync(CancellationToken.None); + var result = await session.CaptureScreenshotAsync(BrowserScreenshotCaptureOptions.Default, CancellationToken.None); Assert.Equal("image-data", result.Data); Assert.Contains("CaptureScreenshot:target-session-1", connection.Calls); @@ -189,7 +222,7 @@ public async Task CaptureScreenshotAsync_IsCanceledWhenSessionIsDisposed() reuseInitialBlankTarget: false, CancellationToken.None); - var captureTask = session.CaptureScreenshotAsync(CancellationToken.None); + var captureTask = session.CaptureScreenshotAsync(BrowserScreenshotCaptureOptions.Default, CancellationToken.None); await connection.ScreenshotCaptureStarted.Task.DefaultTimeout(); var disposeTask = session.DisposeAsync().AsTask(); @@ -387,7 +420,25 @@ public Task NavigateAsync(string sessionId, Uri url, Canc return Task.FromResult(BrowserLogsCommandAck.Instance); } - public async Task CaptureScreenshotAsync(string sessionId, CancellationToken cancellationToken) + public Task EvaluateAsync(string sessionId, string expression, TimeSpan? timeout, CancellationToken cancellationToken) + { + Calls.Add($"Evaluate:{sessionId}:{expression}"); + return Task.FromResult(new BrowserLogsRuntimeEvaluateResult + { + Result = new BrowserLogsCdpProtocolRemoteObject + { + Value = new BrowserLogsCdpProtocolStringValue("""{"action":"evaluate"}""") + } + }); + } + + public Task SendRawCommandAsync(string? sessionId, string method, string? parametersJson, CancellationToken cancellationToken) + { + Calls.Add($"Raw:{sessionId}:{method}:{parametersJson}"); + return Task.FromResult("""{"ok":true}"""); + } + + public async Task CaptureScreenshotAsync(string sessionId, BrowserScreenshotCaptureOptions options, CancellationToken cancellationToken) { Calls.Add($"CaptureScreenshot:{sessionId}"); ScreenshotCaptureStarted.TrySetResult(); diff --git a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs index 47d76da7cbc..92ccfa0f702 100644 --- a/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs +++ b/tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/AtsTypeScriptCodeGeneratorTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#pragma warning disable ASPIREBROWSERLOGS001 // Type is for evaluation purposes only +#pragma warning disable ASPIREBROWSERAUTOMATION001 // Type is for evaluation purposes only using System.Reflection; using Aspire.Hosting.ApplicationModel; @@ -399,18 +399,18 @@ public async Task Scanner_HostingAssembly_AddContainerCapability() } [Fact] - public void Scanner_BrowsersAssembly_WithBrowserLogsCapability() + public void Scanner_BrowsersAssembly_WithBrowserAutomationCapability() { var capabilities = ScanCapabilitiesFromBrowsersAssembly(); - var withBrowserLogs = capabilities.FirstOrDefault(c => c.CapabilityId == "Aspire.Hosting.Browsers/withBrowserLogs"); - Assert.NotNull(withBrowserLogs); - Assert.Equal("withBrowserLogs", withBrowserLogs.MethodName); - Assert.Equal("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints", withBrowserLogs.TargetTypeId); - Assert.Contains(withBrowserLogs.Parameters, p => p.Name == "browser" && p.Type?.TypeId == "string" && p.IsOptional); - Assert.Contains(withBrowserLogs.Parameters, p => p.Name == "profile" && p.Type?.TypeId == "string" && p.IsOptional); - Assert.Contains(withBrowserLogs.Parameters, p => p.Name == "userDataMode" && p.IsOptional); - Assert.True(withBrowserLogs.ReturnsBuilder); + var withBrowserAutomation = capabilities.FirstOrDefault(c => c.CapabilityId == "Aspire.Hosting.Browsers/withBrowserAutomation"); + Assert.NotNull(withBrowserAutomation); + Assert.Equal("withBrowserAutomation", withBrowserAutomation.MethodName); + Assert.Equal("Aspire.Hosting/Aspire.Hosting.ApplicationModel.IResourceWithEndpoints", withBrowserAutomation.TargetTypeId); + Assert.Contains(withBrowserAutomation.Parameters, p => p.Name == "browser" && p.Type?.TypeId == "string" && p.IsOptional); + Assert.Contains(withBrowserAutomation.Parameters, p => p.Name == "profile" && p.Type?.TypeId == "string" && p.IsOptional); + Assert.Contains(withBrowserAutomation.Parameters, p => p.Name == "userDataMode" && p.IsOptional); + Assert.True(withBrowserAutomation.ReturnsBuilder); } [Fact] @@ -931,7 +931,7 @@ private static List ScanCapabilitiesFromHostingAssembly() private static List ScanCapabilitiesFromBrowsersAssembly() { - var browsersAssembly = typeof(global::Aspire.Hosting.BrowserLogsBuilderExtensions).Assembly; + var browsersAssembly = typeof(global::Aspire.Hosting.BrowserAutomationBuilderExtensions).Assembly; var result = AtsCapabilityScanner.ScanAssembly(browsersAssembly); return result.Capabilities; }