Skip to content

Match WaitForURLAsync strings against the whole URL - #34

Merged
TorreyBetts merged 4 commits into
mainfrom
fix/wait-for-url-exact
Oct 24, 2026
Merged

TorreyBetts merged 4 commits into
mainfrom
fix/wait-for-url-exact

Conversation

@TorreyBetts

Copy link
Copy Markdown
Contributor

WaitForURLAsync(string) treated a plain string as "the URL contains this", so WaitForURLAsync("https://example.test/") returned at once from /account, /orders was satisfied by /orders/42/edit, and ?page=1 by ?page=10. The wait passed silently and the test went on against a page that hadn't loaded. Playwright waits for that exact URL, or a glob over the whole URL, and that is what Motus does now.

While writing the tests, three more problems in the same wait turned up, and they are fixed here because the wait isn't usable without them:

  • IFrame.WaitForURLAsync waited on the page's URL, not the frame's.
  • A URL change that keeps the document (history.pushState, replaceState, a fragment) never updated Url at all, so a single-page application never appeared to move and a wait for its next route always timed out.
  • A timeout surfaced as TaskCanceledException rather than the documented WaitTimeoutException.

What changed

  • IPage.WaitForURLAsync(string) and IFrame.WaitForURLAsync(string): a string with no * has to equal the whole URL; a string with * is a glob that has to cover the whole URL. The glob syntax is unchanged from routes: each * matches any run of characters including /, so * and ** are the same, and nothing else is special.
  • New overloads on IPage and IFrame: WaitForURLAsync(Regex, NavigationOptions?) and WaitForURLAsync(Func<string, bool>, NavigationOptions?), with XML docs.
  • Expect.That(page).ToHaveUrlAsync(string) uses the same whole-URL matcher, so a wait and an assertion given the same string agree.
  • The wait returns at once when the URL already matches. It listens to frame navigation events as well as polling every 100ms, so a URL the page only passes through (a redirect hop, a quick pushState) is still seen. The predicate is only ever called from the waiting loop, never concurrently.
  • A frame's wait checks that frame's own URL.
  • Same-document navigations update IFrame.Url and raise IPage.FrameNavigated. On CDP this is Page.navigatedWithinDocument. On BiDi, which reports it as two events, BiDiEventMap can now hold more than one translation per CDP event and BiDiSession merges them: browsingContext.historyUpdated and browsingContext.fragmentNavigated. Firefox accepts a context-scoped subscription to fragmentNavigated and then never delivers it (checked against Firefox 158 with the protocol log), so that one event is subscribed for all contexts; events are still routed by the context they name.
  • A timeout throws WaitTimeoutException whose message and LastEvaluatedValue carry the URL the page was on.
  • Docs: the navigation section of getting-started.md, the URL pattern section of the network interception guide, the page assertions table in selectors-and-assertions.md, and the Playwright migration guide.

What I deliberately left alone: routes (IPage.RouteAsync, IBrowserContext.RouteAsync, the MCP route_* tools) and WaitForRequestAsync / WaitForResponseAsync keep their documented exact, glob, or substring matching. Those are documented as one pattern syntax, and changing them silently would break existing mocks. I also didn't adopt Playwright's narrower * (stops at /) or {a,b} brace lists, since the glob is shared with routes and the docs' own examples (*/api/status.json, *iana.org*) depend on * crossing /. A Regex covers those cases.

Behavior changes

  • WaitForURLAsync(string) on IPage and IFrame no longer matches a URL that only contains the string. A string with no * has to be the whole URL. To keep matching part of a URL, use a glob that covers the rest, such as "**/orders**", or pass a Regex such as new Regex("/orders").
  • ToHaveUrlAsync(string) changes the same way. Use a glob such as "**/orders**" for a partial match.
  • IFrame.WaitForURLAsync waits on the frame's own URL. It used to check the page's URL.
  • IPage.Url and IFrame.Url now follow history.pushState, history.replaceState and fragment navigations, and IPage.FrameNavigated is raised for them (as in Playwright). Code that read Url after a client-side route change used to get the old URL.
  • A WaitForURLAsync timeout throws WaitTimeoutException instead of TaskCanceledException.
  • IPage and IFrame gain two WaitForURLAsync overloads, so a class implementing either interface outside Motus has to add them.

Tests

  • New UrlMatchingTests: exact URL, a site's root against a deeper page, a path prefix, ?page=1 against ?page=10, case sensitivity, globs that cover or only partly cover the URL, ?, braces and brackets staying literal, and route matching keeping its substring form.
  • New PageWaitForUrlTests against a fake CDP socket: already matching, timeout type and message, string, glob, regex and predicate waits, a predicate that throws, null arguments, a URL passed through between polls, Page.navigatedWithinDocument for history and fragment changes, and frame waits that ignore the page's URL (and the page ignoring a child frame's).
  • PageAssertionsTests: ToHaveUrlAsync no longer passes on a prefix.
  • BiDiEventMapTests and BiDiSessionTests: both BiDi sources for Page.navigatedWithinDocument, the payload translation, the all-contexts subscription for fragmentNavigated, and delivery surviving one of the two subscriptions being refused.
  • New WaitForUrlIntegrationTests (Chromium, 12 tests) over a loopback HTTP server: exact versus containing, already matching, globs, regex, predicate, pushState, fragment, and frame waits by string, regex and predicate including a pushState inside the frame. WaitForUrlFirefoxIntegrationTests (3 tests) covers exact matching, pushState and a fragment on Firefox.

Before the fix, 7 of the 8 string-only Chromium integration tests failed (the contains match, pushState, fragment, the frame checking the page's URL, and TaskCanceledException on timeout). The Firefox fragment test failed until the all-contexts subscription went in.

Results:

  • Unit suite (TestCategory!=Integration, excluding Samples), net10.0 and net8.0: all passed (Motus.Tests 1101, Mcp 366, Cli 303 with 2 skipped, Recorder 105, Codegen 59, Analyzers 29, Testing.xUnit 8).
  • Integration, Chromium and Firefox, net10.0 and net8.0: the new tests plus PageIntegrationTests, OutOfProcessFrameTests, FrameOrderTests, FrameSwapTests, NewPageFrameTreeTests, FrameLocatorTests, AssertionRetryIntegrationTests, BrowserContextRouteIntegrationTests, PageNetworkIntegrationTests, FirefoxIntegrationTests and the MCP FrameToolsIntegrationTests. net8.0: 99/99 and 11/11. net10.0: 98/99 and 11/11. The one failure was FrameOrderTests.AfterAFrameDetaches_TheSurvivorsKeepTheirOrder timing out waiting for frames; it passed when I reran that class and passed on net8.0.

Fixes #24

A plain string passed to WaitForURLAsync matched any URL that contained
it, so waiting for a site's root returned at once from every page on the
site, and "?page=1" was satisfied by "?page=10". The wait passed silently
and the test went on against a page that had not loaded.

A string with no wildcard now has to equal the URL, and a string with a
wildcard is a glob over the whole URL, using the same glob syntax routes
already use. WaitForURLAsync gains Regex and predicate overloads on IPage
and IFrame for anything a glob cannot express. ToHaveUrlAsync uses the
same matcher, so a wait and an assertion given the same string agree.
Routes and the request and response waits keep their documented
substring form.

Three gaps surfaced while testing the wait and are fixed with it. A
frame's wait checked the page's URL instead of the frame's. URL changes
that keep the document (history.pushState, fragments) never updated the
frame URL, so a single-page application never appeared to move; CDP's
Page.navigatedWithinDocument is now handled, and on BiDi it is fed by
both browsingContext.fragmentNavigated and browsingContext.historyUpdated.
And a timeout surfaced as TaskCanceledException rather than the
documented WaitTimeoutException. The wait now listens for navigation
events as well as polling, so a URL the page only passes through is not
missed.

Signed-off-by: Torrey Betts <torrey.betts@gmail.com>
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Summary

Summary
Generated on: 10/24/2026 - 19:53:09
Coverage date: 10/24/2026 - 19:48:12 - 10/24/2026 - 19:52:59
Parser: MultiReport (38x Cobertura)
Assemblies: 12
Classes: 574
Files: 291
Line coverage: 82.4% (9791 of 11880)
Covered lines: 9791
Uncovered lines: 2089
Coverable lines: 11880
Total lines: 42566
Branch coverage: 70.8% (3645 of 5144)
Covered branches: 3645
Total branches: 5144
Method coverage: Feature is only available for sponsors

Coverage

Motus - 80.9%
Name Line Branch
Motus 80.9% 72%
Motus.AccessibilityAuditHook 62% 40%
Motus.AccessibilityAXNode 100%
Motus.AccessibilityAXNodeSimple 100%
Motus.AccessibilityAXProperty 100%
Motus.AccessibilityAXRelatedNode 100%
Motus.AccessibilityAXValue 100%
Motus.AccessibilityAXValueSimple 100%
Motus.AccessibilityEnableResult 100%
Motus.AccessibilityGetFullAXTreeParams 100%
Motus.AccessibilityQueryAXTreeParams 100%
Motus.AccessibilityRoleNames 100% 50%
Motus.AccessibilityRuleCollection 100%
Motus.AccessibilityRuleEngine 100% 100%
Motus.AccessibilityRulesPlugin 75%
Motus.AccessibilitySelectorHelper 0% 0%
Motus.AccessibilityTreeQuery 98.8% 98.6%
Motus.AccessibilityTreeResult 100%
Motus.AccessibilityViolationSink 100% 100%
Motus.ActionContext 100%
Motus.AltTextAccessibilityRule 94.1% 100%
Motus.Assertions.AccessibilityAssertionOptions 100%
Motus.Assertions.AssertionRetryHelper 100% 75%
Motus.Assertions.Expect 100%
Motus.Assertions.LocatorAssertions 90.9% 100%
Motus.Assertions.PageAssertions 98.1% 85.7%
Motus.Assertions.ResponseAssertions 100% 100%
Motus.AttachedToTargetTranslation 100% 50%
Motus.BackendNodeIdSelectorStrategy 50% 0%
Motus.BiDiBrowsingContextCloseParams 0%
Motus.BiDiBrowsingContextCreateParams 0%
Motus.BiDiBrowsingContextCreateResult 0%
Motus.BiDiBrowsingContextNavigateParams 0%
Motus.BiDiBrowsingContextNavigateResult 0%
Motus.BiDiBrowsingContextReloadParams 0%
Motus.BiDiCommandEnvelope 100%
Motus.BiDiDisconnectedException 50%
Motus.BiDiEventMap 100% 83.3%
Motus.BiDiInboundDiscriminator 100%
Motus.BiDiInputAction 100%
Motus.BiDiInputActionSequence 100%
Motus.BiDiInputPerformActionsParams 100%
Motus.BiDiInputSourceParameters 100%
Motus.BiDiKeyValues 100% 100%
Motus.BiDiNetworkAddInterceptParams 100%
Motus.BiDiNetworkAddInterceptResult 0%
Motus.BiDiNetworkBytesValue 100%
Motus.BiDiNetworkContinueRequestParams 100%
Motus.BiDiNetworkFailRequestParams 100%
Motus.BiDiNetworkHeader 0%
Motus.BiDiNetworkProvideResponseParams 100%
Motus.BiDiNetworkUrlPattern 0%
Motus.BiDiProtocolException 75%
Motus.BiDiScriptCallFunctionParams 100%
Motus.BiDiScriptCallFunctionResult 0%
Motus.BiDiScriptEvaluateParams 0%
Motus.BiDiScriptEvaluateResult 0%
Motus.BiDiScriptExceptionDetails 0%
Motus.BiDiScriptLocalValue 0%
Motus.BiDiScriptRemoteValue 0%
Motus.BiDiScriptTarget 100%
Motus.BiDiSession 100% 100%
Motus.BiDiSessionCapabilitiesRequest 0%
Motus.BiDiSessionNewParams 0%
Motus.BiDiSessionNewResult 0%
Motus.BiDiSessionRegistry 84.6%
Motus.BiDiSessionStatusResult 0%
Motus.BiDiSessionSubscribeParams 100%
Motus.BiDiTranslationRegistry 100%
Motus.BiDiTransport 98.2% 90%
Motus.Browser 54% 42.8%
Motus.BrowserCloseResult 100%
Motus.BrowserCloseTranslation 50%
Motus.BrowserContext 74% 35.2%
Motus.BrowserFinder 50.4% 65.8%
Motus.BrowserGetVersionResult 100%
Motus.BrowserGetVersionTranslation 83.3% 50%
Motus.BrowserGrantPermissionsParams 100%
Motus.BrowserGrantPermissionsResult 100%
Motus.BrowserHeartbeat 100%
Motus.BrowserLease 100%
Motus.BrowserOutputDrain 100% 100%
Motus.BrowserPool 20% 0%
Motus.BrowserResetPermissionsResult 0%
Motus.BuiltinSelectorsPlugin 75%
Motus.CapabilityGuard 88.8% 83.3%
Motus.CdpCommandEnvelope 100%
Motus.CdpDisconnectedException 50%
Motus.CdpEndpointPoller 75% 50%
Motus.CdpErrorPayload 100%
Motus.CdpInboundEnvelope 100%
Motus.CdpPipeSocket 96.5% 87.5%
Motus.CdpProtocolException 75%
Motus.CdpSession 100% 83.3%
Motus.CdpSessionRegistry 100%
Motus.CdpSocket 75% 0%
Motus.CdpTransport 100% 97%
Motus.ChromiumArgs 100% 100%
Motus.ColorContrastRule 93.1% 89.2%
Motus.ComputedStyleCollector 100%
Motus.ConfigMerge 37.7% 45.8%
Motus.ConsoleApiCalledTranslation 84.2% 25%
Motus.ContrastCalculator 91% 72.5%
Motus.CoverageAggregator 95% 87.2%
Motus.CoverageCollector 95.2% 100%
Motus.CoverageRemapper 96.2% 87.8%
Motus.CoverageSink 72.7% 50%
Motus.CoverageThresholdResult 100%
Motus.CoverageThresholds 100% 100%
Motus.CssEnableResult 0%
Motus.CssRuleUsageEntry 0%
Motus.CssSelectorStrategy 100%
Motus.CssStartRuleUsageTrackingResult 0%
Motus.DebuggerDisableResult 0%
Motus.DetachedFromTargetTranslation 25% 0%
Motus.Dialog 100%
Motus.DialogClosedTranslation 100%
Motus.DialogOpeningTranslation 100% 50%
Motus.DomBoxModel 0%
Motus.DomContentLoadedTranslation 50%
Motus.DomDebuggerEventListener 0%
Motus.DomDebuggerGetEventListenersParams 0%
Motus.DomDebuggerGetEventListenersResult 0%
Motus.DomDescribeNodeParams 100%
Motus.DomDocumentNode 0%
Motus.DomEnableResult 0%
Motus.DomFocusResult 0%
Motus.DomGetBoxModelParams 0%
Motus.DomGetNodeForLocationParams 100%
Motus.DomGetNodeForLocationResult 100%
Motus.DomGetOuterHtmlParams 100%
Motus.DomHideHighlightResult 0%
Motus.DomHighlightConfig 0%
Motus.DomHighlightNodeParams 0%
Motus.DomHighlightNodeResult 0%
Motus.DomNodeDescription 100%
Motus.DomResolveNodeParams 100%
Motus.DomScrollIntoViewIfNeededResult 0%
Motus.DomSetFileInputFilesParams 100%
Motus.DomSetFileInputFilesResult 100%
Motus.Download 40% 0%
Motus.DuplicateIdRule 88.8% 90%
Motus.ElementHandle 100%
Motus.EmptyButtonRule 94.1% 100%
Motus.EmptyLinkRule 94.1% 100%
Motus.EmulationSetDeviceMetricsOverrideParams 100%
Motus.EmulationSetDeviceMetricsOverrideResult 100%
Motus.EmulationSetEmulatedMediaParams 100%
Motus.EmulationSetEmulatedMediaResult 100%
Motus.EmulationSetGeolocationOverrideParams 0%
Motus.EmulationSetGeolocationOverrideResult 0%
Motus.EmulationSetLocaleOverrideResult 100%
Motus.EmulationSetTimezoneOverrideResult 100%
Motus.EmulationSetUserAgentOverrideParams 100%
Motus.EmulationSetUserAgentOverrideResult 100%
Motus.FailureArtifactName 91.3% 94.7%
Motus.FetchAuthChallenge 0%
Motus.FetchAuthChallengeResponse 0%
Motus.FetchAuthRequiredEvent 0%
Motus.FetchContinueRequestParams 100%
Motus.FetchContinueRequestResult 100%
Motus.FetchContinueRequestTranslation 67.8% 42.8%
Motus.FetchContinueWithAuthParams 0%
Motus.FetchContinueWithAuthResult 0%
Motus.FetchDisableResult 100%
Motus.FetchEnableParams 100%
Motus.FetchEnableResult 100%
Motus.FetchEnableTranslation 58.8% 30%
Motus.FetchFailRequestParams 100%
Motus.FetchFailRequestResult 100%
Motus.FetchFailRequestTranslation 71.4% 50%
Motus.FetchFulfillRequestParams 100%
Motus.FetchFulfillRequestResult 100%
Motus.FetchFulfillRequestTranslation 67.8% 42.8%
Motus.FetchRequestPattern 100%
Motus.FetchRequestPausedEvent 100%
Motus.FileChooser 0% 0%
Motus.FirefoxArgs 100% 100%
Motus.FirefoxProfileManager 100% 100%
Motus.FragmentNavigatedTranslation 100% 50%
Motus.Frame 45.9% 12.5%
Motus.FrameNavigatedTranslation 100% 50%
Motus.GetBySelectors 100% 100%
Motus.GetBySelectorStrategy 96.9% 96.1%
Motus.HarContent 100%
Motus.HarEntry 100%
Motus.HarLog 100%
Motus.HarPage 0%
Motus.HarRecorder 96.4% 79.5%
Motus.HarRequest 100%
Motus.HarResponse 100%
Motus.HarTimings 100%
Motus.HeaderCollection 95.8% 100%
Motus.HeadingHierarchyRule 91.1% 80%
Motus.HistoryUpdatedTranslation 100% 50%
Motus.IBiDiEventTranslation 100%
Motus.IMotusSession 0%
Motus.InputDispatchKeyEventParams 100%
Motus.InputDispatchKeyEventResult 100%
Motus.InputDispatchKeyEventTranslation 100% 83.3%
Motus.InputDispatchMouseEventParams 100%
Motus.InputDispatchMouseEventResult 100%
Motus.InputDispatchMouseEventTranslation 72.5% 33.3%
Motus.InputDispatchTouchEventParams 100%
Motus.InputDispatchTouchEventResult 100%
Motus.InputDispatchTouchEventTranslation 83.7% 50%
Motus.InputInsertTextResult 100%
Motus.InputTouchPoint 100%
Motus.InstalledBrowsers 81.8% 100%
Motus.IoCloseResult 100%
Motus.IoReadParams 100%
Motus.IoReadResult 100%
Motus.JsHandle 66.6% 30%
Motus.JsonBuilder 100% 66.6%
Motus.Keyboard 100% 100%
Motus.KeyDefinitions 100% 100%
Motus.LifecycleHookCollection 100%
Motus.LoadEventFiredTranslation 100%
Motus.Locator 88.5% 78.6%
Motus.MappingSegment 100%
Motus.MissingDocumentLanguageRule 92.8% 100%
Motus.MissingLandmarkRule 93.7% 100%
Motus.MjpegAviWriter 100% 50%
Motus.MotusAccessibilityConfig 100%
Motus.MotusAssertionsConfig 100%
Motus.MotusConfigLoader 89.4% 88.5%
Motus.MotusContextConfig 100%
Motus.MotusCoverageConfig 100%
Motus.MotusCoverageCssConfig 0%
Motus.MotusCoverageJsConfig 0%
Motus.MotusFailureConfig 100%
Motus.MotusFlakyConfig 100%
Motus.MotusLaunchConfig 100%
Motus.MotusLauncher 56.2% 100%
Motus.MotusLocatorConfig 100%
Motus.MotusMcpConfig 100%
Motus.MotusPerformanceConfig 100%
Motus.MotusRecorderConfig 100%
Motus.MotusReporterConfig 100%
Motus.MotusRequest 100%
Motus.MotusResponse 100% 50%
Motus.MotusRootConfig 100%
Motus.MotusRoute 58% 45.6%
Motus.MotusShardConfig 100%
Motus.MotusVideo 100%
Motus.MotusViewportConfig 100%
Motus.Mouse 92.8% 78.5%
Motus.NetworkClearBrowserCookiesResult 0%
Motus.NetworkCookieData 100%
Motus.NetworkEmulateNetworkConditionsParams 100%
Motus.NetworkEmulateNetworkConditionsResult 100%
Motus.NetworkEnableParams 100%
Motus.NetworkEnableResult 100%
Motus.NetworkLoadingFailedEvent 100%
Motus.NetworkLoadingFinishedEvent 100%
Motus.NetworkManager 97.4% 75%
Motus.NetworkRequestData 100%
Motus.NetworkRequestWillBeSentEvent 100%
Motus.NetworkResponseData 100%
Motus.NetworkResponseReceivedEvent 100%
Motus.NetworkSetCookieParams 100%
Motus.NetworkSetExtraHttpHeadersParams 100%
Motus.NetworkSetExtraHttpHeadersResult 100%
Motus.NoOpTranslation 60%
Motus.NullMotusLogger 40%
Motus.NullTracing 0%
Motus.Page 73.6% 70.7%
Motus.PageBringToFrontResult 0%
Motus.PageCaptureScreenshotParams 0%
Motus.PageCaptureScreenshotWithClipParams 100%
Motus.PageClipRect 100%
Motus.PageCreateIsolatedWorldParams 0%
Motus.PageDownloadProgressEvent 0%
Motus.PageDownloadWillBeginEvent 100%
Motus.PageEnableResult 100%
Motus.PageFileChooserOpenedEvent 0%
Motus.PageFrameInfo 100%
Motus.PageFrameTreeNode 100%
Motus.PageGetFrameTreeTranslation 100% 75%
Motus.PageGetNavigationHistoryResult 0%
Motus.PageHandleJavaScriptDialogResult 0%
Motus.PageJavascriptDialogOpeningEvent 100%
Motus.PageNavigatedWithinDocumentEvent 100%
Motus.PageNavigateParams 100%
Motus.PageNavigateResult 100%
Motus.PageNavigateToHistoryEntryResult 0%
Motus.PageNavigateTranslation 100% 50%
Motus.PageReloadResult 100%
Motus.PageReloadTranslation 75% 50%
Motus.PageScreencastFrameAckResult 0%
Motus.PageScreencastFrameEvent 0%
Motus.PageScreencastFrameMetadata 0%
Motus.PageSetInterceptFileChooserDialogResult 0%
Motus.PageStartScreencastParams 100%
Motus.PageStartScreencastResult 100%
Motus.PageStopScreencastResult 0%
Motus.PerformanceBudgetContext 100% 100%
Motus.PerformanceEnableResult 100%
Motus.PerformanceMetricsCollector 94.7% 100%
Motus.PerformanceMetricsSink 100%
Motus.PluginContext 87.5%
Motus.PluginHost 100%
Motus.ProcessStderrAdapter 0%
Motus.ProfilerCoverageRange 0%
Motus.ProfilerEnableResult 0%
Motus.ProfilerFunctionCoverage 0%
Motus.ProfilerScriptCoverage 0%
Motus.ProfilerStartPreciseCoverageParams 0%
Motus.ProfilerStopPreciseCoverageResult 0%
Motus.ProfilerTakePreciseCoverageResult 0%
Motus.ProtocolLog 77.7% 81.4%
Motus.ProtocolLogFile 88.4% 68.7%
Motus.ProtocolLogFormatter 96.5% 93.7%
Motus.ProtocolLogSettings 100% 95.4%
Motus.ProtocolLogSink 86.6% 76.4%
Motus.ReporterCollection 100%
Motus.RequestPausedTranslation 100% 60%
Motus.RequestWillBeSentTranslation 13.3% 0%
Motus.ResponseReceivedTranslation 7.4% 0%
Motus.RoleSelectorStrategy 95.2% 94.4%
Motus.RuntimeAddBindingResult 100%
Motus.RuntimeBindingCalledEvent 0%
Motus.RuntimeCallArgument 100%
Motus.RuntimeCallFunctionOnParams 100%
Motus.RuntimeCallFunctionOnResult 100%
Motus.RuntimeCallFunctionOnTranslation 46.3% 23%
Motus.RuntimeConsoleApiCalledEvent 100%
Motus.RuntimeEnableResult 100%
Motus.RuntimeEvaluateParams 100%
Motus.RuntimeEvaluateResult 100%
Motus.RuntimeEvaluateTranslation 92.3% 50%
Motus.RuntimeExceptionDetails 100%
Motus.RuntimeExceptionThrownEvent 100%
Motus.RuntimeExecutionContextCreatedEvent 100%
Motus.RuntimeExecutionContextDescription 100%
Motus.RuntimeExecutionContextDestroyedEvent 100%
Motus.RuntimeGetPropertiesParams 100%
Motus.RuntimePropertyDescriptor 100%
Motus.RuntimeReleaseObjectResult 0%
Motus.RuntimeReleaseObjectTranslation 50%
Motus.RuntimeRemoteObject 100%
Motus.ScriptResultTranslator 57.6% 22.3%
Motus.SecurityEnableResult 100%
Motus.SecuritySetIgnoreCertificateErrorsResult 100%
Motus.Selectors.DomFingerprint 100%
Motus.Selectors.DomFingerprintBuilder 98% 91.6%
Motus.Selectors.SelectorEntry 100%
Motus.SelectorStrategyHelpers 100%
Motus.SelectorStrategyRegistry 100%
Motus.SourceMap 100%
Motus.SourceMapFetcher 0% 0%
Motus.SourceMapJsonDto 100%
Motus.SourceMapParser 90.3% 77.5%
Motus.SourceMapResolver 93.3% 87.5%
Motus.TargetAttachedToTargetEvent 100%
Motus.TargetAttachToTargetTranslation 62.5% 100%
Motus.TargetCloseTargetTranslation 60% 50%
Motus.TargetCreateBrowserContextParams 100%
Motus.TargetCreateBrowserContextTranslation 50%
Motus.TargetCreateTargetParams 100%
Motus.TargetCreateTargetTranslation 60% 50%
Motus.TargetDetachedFromTargetEvent 100%
Motus.TargetDisposeBrowserContextResult 100%
Motus.TargetDisposeBrowserContextTranslation 50%
Motus.TargetInfo 100%
Motus.TargetSetAutoAttachParams 100%
Motus.TargetSetAutoAttachResult 100%
Motus.TargetSetAutoAttachTranslation 50%
Motus.TargetSetDiscoverTargetsResult 100%
Motus.TestIdSelectorStrategy 100%
Motus.TestMethodNameContext 100%
Motus.TextSelectorStrategy 100%
Motus.Touchscreen 100%
Motus.Tracing 82.6% 77%
Motus.TracingEndResult 100%
Motus.TracingStartParams 100%
Motus.TracingStartResult 100%
Motus.TracingTraceConfig 100%
Motus.TracingTracingCompleteEvent 100%
Motus.UnlabeledFormControlRule 95.4% 100%
Motus.VideoRecorder 90.9% 100%
Motus.Vlq 100% 100%
Motus.WindowsProcessGuard 0% 0%
Motus.XPathSelectorStrategy 100%
System.Text.Json.Serialization.JsonStringEnumConverter`1 0% 0%
System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute 0%
Motus.Abstractions - 87.7%
Name Line Branch
Motus.Abstractions 87.7% 100%
Motus.Abstractions.AccessibilityAuditContext 100%
Motus.Abstractions.AccessibilityAuditResult 100%
Motus.Abstractions.AccessibilityNode 100%
Motus.Abstractions.AccessibilitySnapshot 100%
Motus.Abstractions.AccessibilityViolation 100%
Motus.Abstractions.ActionTimeoutException 50%
Motus.Abstractions.AmbiguousSelectorException 50%
Motus.Abstractions.BrowserFailure 100% 100%
Motus.Abstractions.ComputedStyleInfo 100%
Motus.Abstractions.Cookie 100%
Motus.Abstractions.CoverageData 100% 100%
Motus.Abstractions.CoverageSummary 100%
Motus.Abstractions.ElementNotFoundException 100%
Motus.Abstractions.IBrowser 0%
Motus.Abstractions.LayoutShiftEntry 100%
Motus.Abstractions.MotusAssertionException 50%
Motus.Abstractions.MotusException 100%
Motus.Abstractions.MotusNavigationException 50%
Motus.Abstractions.MotusProtocolException 100%
Motus.Abstractions.MotusSelectorException 100%
Motus.Abstractions.MotusTargetClosedException 100%
Motus.Abstractions.MotusTimeoutException 50%
Motus.Abstractions.MouseButtonOptions 100%
Motus.Abstractions.MouseMoveOptions 100%
Motus.Abstractions.NavigationTimeoutException 50%
Motus.Abstractions.OriginalFileCoverage 100%
Motus.Abstractions.PerformanceBudget 100%
Motus.Abstractions.PerformanceBudgetAttribute 100% 100%
Motus.Abstractions.PerformanceBudgetEntry 100%
Motus.Abstractions.PerformanceBudgetResult 100%
Motus.Abstractions.PerformanceMetrics 100%
Motus.Abstractions.ProxySettings 100%
Motus.Abstractions.RouteContinueOptions 100%
Motus.Abstractions.ScriptCoverage 100%
Motus.Abstractions.StorageState 100%
Motus.Abstractions.StylesheetCoverage 100%
Motus.Abstractions.TestInfo 100%
Motus.Abstractions.TestResult 100%
Motus.Abstractions.TestRunSummary 100%
Motus.Abstractions.TestSuiteInfo 100%
Motus.Abstractions.WaitTimeoutException 50%
Motus.Analyzers - 91%
Name Line Branch
Motus.Analyzers 91% 72.4%
Motus.Analyzers.Analyzers.DeprecatedSelectorAnalyzer 90.6% 61.1%
Motus.Analyzers.Analyzers.FragileSelectorAnalyzer 92.1% 65%
Motus.Analyzers.Analyzers.HardcodedDelayAnalyzer 96.1% 81.2%
Motus.Analyzers.Analyzers.MissingDisposalAnalyzer 100% 100%
Motus.Analyzers.Analyzers.NavigationWaitAnalyzer 93.4% 77%
Motus.Analyzers.Analyzers.NonAwaitedCallAnalyzer 90.9% 75%
Motus.Analyzers.Analyzers.UnusedLocatorAnalyzer 94.5% 64.2%
Motus.Analyzers.CodeFixes.AddAwaitCodeFix 0%
Motus.Analyzers.CodeFixes.ReplaceDelayCodeFix 56.2% 50%
Motus.Analyzers.CodeFixes.WrapAwaitUsingCodeFix 0%
Motus.Analyzers.SymbolHelper 100% 82.1%
Motus.Cli - 82.2%
Name Line Branch
Motus.Cli 82.2% 61.5%
Motus.Cli.Commands.BrowserPathHelper 88.8% 50%
Motus.Cli.Commands.CheckSelectorsCommand 100%
Motus.Cli.Commands.CodegenCommand 93.8% 0%
Motus.Cli.Commands.InstallCommand 100%
Motus.Cli.Commands.McpCommand 96.8% 60.8%
Motus.Cli.Commands.PdfCommand 100%
Motus.Cli.Commands.RecordCommand 94.2%
Motus.Cli.Commands.RunCommand 94.4% 16.6%
Motus.Cli.Commands.ScreenshotCommand 100%
Motus.Cli.Commands.ShardCommand 0%
Motus.Cli.Commands.TraceCommand 0%
Motus.Cli.Commands.TrxCommand 100%
Motus.Cli.Commands.UpdateProtocolCommand 100%
Motus.Cli.Services.BrowserInstaller 2.1% 0%
Motus.Cli.Services.CheckSelectorsRunner 0% 0%
Motus.Cli.Services.DomainDiff 100%
Motus.Cli.Services.FingerprintCandidate 100%
Motus.Cli.Services.FingerprintMatch 0%
Motus.Cli.Services.FingerprintScanner 0% 0%
Motus.Cli.Services.FlakeHistoryReporter 86.6% 100%
Motus.Cli.Services.InteractiveRepairApplier 85.7% 75%
Motus.Cli.Services.LocatorDispatcher 26.3% 10.8%
Motus.Cli.Services.ParsedSelector 100%
Motus.Cli.Services.ProtocolDiff 100% 100%
Motus.Cli.Services.ProtocolDiffer 97.2% 95.4%
Motus.Cli.Services.ProtocolUpdater 0% 0%
Motus.Cli.Services.RepairSuggestion 100%
Motus.Cli.Services.RepairSuggestionPipeline 75% 75%
Motus.Cli.Services.Reporters.CoberturaReporter 62.5% 54.1%
Motus.Cli.Services.Reporters.CompositeReporter 100%
Motus.Cli.Services.Reporters.ConsoleReporter 63.1% 50%
Motus.Cli.Services.Reporters.CoverageConsoleReporter 88.6% 70.8%
Motus.Cli.Services.Reporters.CoverageHtmlReporter 91.4% 88.2%
Motus.Cli.Services.Reporters.CoverageReporterFactory 100% 95.8%
Motus.Cli.Services.Reporters.HtmlReporter 92.3% 100%
Motus.Cli.Services.Reporters.JUnitReporter 100% 100%
Motus.Cli.Services.Reporters.ReporterFactory 100% 100%
Motus.Cli.Services.Reporters.TrxReporter 100%
Motus.Cli.Services.SelectorCheckResult 100%
Motus.Cli.Services.SelectorCheckTablePrinter 93.6% 78.3%
Motus.Cli.Services.SelectorManifestWriter 100% 87.5%
Motus.Cli.Services.SelectorParser 94.2% 88.8%
Motus.Cli.Services.SelectorParseResult 100%
Motus.Cli.Services.SelectorParseWarning 100%
Motus.Cli.Services.SelectorRewriter 77.6% 67.1%
Motus.Cli.Services.ShardMerger 91.3% 66.4%
Motus.Cli.Services.ShardMergeResult 100% 100%
Motus.Cli.Services.ShardSelector 93.3% 86.3%
Motus.Cli.Services.SuggestionBuilder 100% 91.6%
Motus.Cli.Services.TestDiscovery 95.3% 88.2%
Motus.Cli.Services.TestRunner 84% 81.2%
Motus.Codegen - 89.8%
Name Line Branch
Motus.Codegen 89.8% 70.6%
Motus.Codegen.CdpGenerator 100%
Motus.Codegen.Emit.DomainEmitter 84.9% 71.4%
Motus.Codegen.Emit.NamingHelper 100% 92.8%
Motus.Codegen.Emit.PluginRegistryEmitter 100% 100%
Motus.Codegen.Emit.TypeResolver 75% 55%
Motus.Codegen.Model.PluginTypeInfo 44.4% 0%
Motus.Codegen.Parser.CdpSchemaParser 93.4% 85.2%
Motus.Codegen.PluginDiscoveryGenerator 97.5% 91.1%
Motus.Mcp - 91.9%
Name Line Branch
Motus.Mcp 91.9% 80.2%
Motus.Mcp.AccessibilityTools 100% 50%
Motus.Mcp.ActionReport 89.7% 69.4%
Motus.Mcp.ActionRunner 100%
Motus.Mcp.ActivePageService 88.8% 100%
Motus.Mcp.BrowserSessionManager 78.2% 75%
Motus.Mcp.ConsoleEntry 50% 0%
Motus.Mcp.ConsoleService 96.8% 94.4%
Motus.Mcp.ConsoleTools 100% 100%
Motus.Mcp.ContextTools 92.8% 87.5%
Motus.Mcp.CoordinateTools 100%
Motus.Mcp.CoreTools 100%
Motus.Mcp.DialogNotice 97.3% 94.7%
Motus.Mcp.DialogService 100% 95%
Motus.Mcp.FrameCollector 100% 100%
Motus.Mcp.InlineFrames 75% 100%
Motus.Mcp.InteractionTools 66.6% 13.8%
Motus.Mcp.LogText 90% 83.3%
Motus.Mcp.McpServerConfiguration 100% 81.8%
Motus.Mcp.McpServerHost 0%
Motus.Mcp.McpServerLaunchOptions 85% 87.5%
Motus.Mcp.McpSessionBundle 100% 50%
Motus.Mcp.McpSessionRegistry 83.3% 50%
Motus.Mcp.NetworkEntry 100% 87.5%
Motus.Mcp.NetworkService 91.5% 90.6%
Motus.Mcp.NetworkTools 100% 100%
Motus.Mcp.PageDescription 100% 100%
Motus.Mcp.PageSnapshotService 94.1% 92.8%
Motus.Mcp.PageTools 100% 100%
Motus.Mcp.PerformanceTools 100% 100%
Motus.Mcp.SecurityPolicy 88.1% 82.2%
Motus.Mcp.SerializedSnapshot 100%
Motus.Mcp.SnapshotNotTakenException 100%
Motus.Mcp.SnapshotSerializer 94.9% 91.3%
Motus.Mcp.StaleRefException 100%
Motus.Mcp.ToolArguments 92% 96.8%
Motus.Mcp.ToolCapabilities 100% 80%
Motus.Mcp.ToolResultHelper 100% 100%
Motus.Mcp.Http - 98.6%
Name Line Branch
Motus.Mcp.Http 98.6% 80%
Motus.Mcp.Http.McpHttpServerHost 98.6% 80%
Motus.Recorder - 77.8%
Name Line Branch
Motus.Recorder 77.8% 70.8%
Motus.Recorder.ActionCapture.ActionCaptureEngine 45.1% 28.2%
Motus.Recorder.ActionCapture.InputStateMachine 99.4% 91.7%
Motus.Recorder.ActionCapture.RecorderScript 100%
Motus.Recorder.CodeEmit.ActionLineEmitter 79.6% 77.5%
Motus.Recorder.CodeEmit.CodeEmitter 83.3% 78.9%
Motus.Recorder.CodeEmit.EmittedLocator 100%
Motus.Recorder.CodeEmit.FrameworkTemplate 97.4% 83.3%
Motus.Recorder.PageAnalysis.DiscoveredElement 100%
Motus.Recorder.PageAnalysis.MemberNameDeriver 94.8% 90.7%
Motus.Recorder.PageAnalysis.PageAnalysisEngine 0.7% 0%
Motus.Recorder.PageAnalysis.PageClassNameDeriver 91.3% 88.8%
Motus.Recorder.PageAnalysis.PageElementInfo 100%
Motus.Recorder.PageAnalysis.SelectorStrategyOrdering 100% 100%
Motus.Recorder.PomEmit.FormGrouper 90.3% 49.3%
Motus.Recorder.PomEmit.PomEmitter 95.6% 86.3%
Motus.Recorder.Records.ActionRecord 100%
Motus.Recorder.Records.CheckAction 100%
Motus.Recorder.Records.ClickAction 100%
Motus.Recorder.Records.DialogAction 100%
Motus.Recorder.Records.FileUploadAction 100%
Motus.Recorder.Records.FillAction 100%
Motus.Recorder.Records.KeyPressAction 100%
Motus.Recorder.Records.NavigationAction 100%
Motus.Recorder.Records.ResolvedAction 100%
Motus.Recorder.Records.ScrollAction 100%
Motus.Recorder.Records.SelectAction 100%
Motus.Recorder.SelectorInference.SelectorInferenceEngine 100% 100%
Motus.Recorder.SelectorInference.SelectorInferenceResult 100%
Motus.Testing - 75.4%
Name Line Branch
Motus.Testing 75.4% 61.1%
Motus.Testing.BrowserFixture 88.6% 78.5%
Motus.Testing.FailureTracing 30.7% 0%
Motus.Testing.MSTest - 79%
Name Line Branch
Motus.Testing.MSTest 79% 79.4%
Motus.Testing.MSTest.MotusTestBase 70.4% 75%
Motus.Testing.MSTest.MotusTestClassAttribute 100% 100%
Motus.Testing.MSTest.MotusTestMethodAttribute 100% 87.5%
Motus.Testing.MSTest.v4 - 22.8%
Name Line Branch
Motus.Testing.MSTest.v4 22.8% 22.2%
Motus.Testing.MSTest.MotusTestBase 0% 0%
Motus.Testing.MSTest.MotusTestClassAttribute 100% 100%
Motus.Testing.MSTest.MotusTestMethodAttribute 100% 60%
Motus.Testing.xUnit - 63.6%
Name Line Branch
Motus.Testing.xUnit 63.6% 33.3%
Motus.Testing.xUnit.BrowserContextFixture 71.4% 33.3%
Motus.Testing.xUnit.BrowserContextFixture`1 100%
Motus.Testing.xUnit.DefaultBrowser 0%
Motus.Testing.xUnit.SharedBrowserFixture 25%

…form

WaitForURLAsync and ToHaveUrlAsync now require a plain string to equal the
URL, but the browser reports a URL in its normal form: a bare origin gains a
trailing slash and the host is lower case. A base URL as it is usually
written, such as http://localhost:5000, therefore never matched the page it
names and the wait ran out its timeout.

An http or https pattern that doesn't equal the URL as given is now also
compared in its normalized form. The match never widens: a path, query or
fragment still has to be the same.

Signed-off-by: Torrey Betts <torrey.betts@gmail.com>
With the substring match gone, a string without a scheme such as "/dashboard" could no longer match anything, because the browser always reports an absolute URL. A context with a BaseURL navigates with GotoAsync("/dashboard"), and waiting or asserting with the same relative string used to work through the substring match; after the switch to whole-URL matching it timed out every time.

WaitForURLAsync on a page or frame and ToHaveUrlAsync now resolve such a string against the base URL first, the way GotoAsync resolves the address it loads, and then compare the whole URL. Globs, absolute URLs, and any string when no base URL is set are compared as written.

Signed-off-by: Torrey Betts <torrey.betts@gmail.com>
Brings in #31, #32, #33, #35, #36, #37, #38 and #40. Everything merged cleanly; the frame navigation handlers in Page.Events.cs sit next to the execution context tracking from #33 without touching it, and same-document navigations still leave execution contexts alone.

Signed-off-by: Torrey Betts <torrey.betts@gmail.com>
@TorreyBetts
TorreyBetts merged commit 740c33d into main Oct 24, 2026
5 checks passed
@TorreyBetts
TorreyBetts deleted the fix/wait-for-url-exact branch October 24, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WaitForURLAsync treats a plain string as "contains", so waiting for a site's root matches every page on it

1 participant