diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 9f6f556e..f5d7df5c 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -31,16 +31,16 @@ Names are C# versions of the JavaScript API: `agent.act` is `ActAsync`, `screen. - NUnit `[Test]`, `[SetUp]`, `[TearDown]`, `[OneTimeSetUp]`, and `[OneTimeTearDown]` - `Assert.Ignore`, `[Retry]`, `[Timeout]`, and `[Category]` -- Screen queries: role, text, label, test id, placeholder, display value, `filter` (`hasText` and `has`), `or`, `first`, `last`, `nth`. A locator offers every query kind, scoped to its matches' descendants. Every query takes a `TextMatch`: a string or a .NET `Regex`, tested against the whitespace-normalized text. A string matches the whole text, case-sensitively, unless `exact` is false, which makes it a case-insensitive substring, as upstream. `RoleOptions` carries `name`, `checked`, `disabled`, `selected`, `expanded`, `pressed`, and `level`, and `img` is read as `image`. A role query never matches a hidden node; the other kinds keep hidden nodes unless `Visible` is set, as upstream. `getByLabel` matches any node whose accessible name matches, as the upstream reference resolver does. Options are C# objects (`RoleOptions`, `TextMatchOptions`) instead of object literals, and `filter({ hasText, has })` is the two overloads `Filter(TextMatch)` and `Filter(Locator)`. Regular expressions use .NET syntax, not ECMAScript. `GetByTestId` also takes `Exact`, which upstream's `getByTestId` does not. `locator.or(other)` (upstream #1049) is `Or(Locator)`: a `union` expression that matches either branch once, in document order, and stays strict (`STRICT_MODE` where upstream says `LOCATOR_AMBIGUOUS`; add `First()` when both may match). Both locators must come from the same screen and attempt, else `INVALID_LOCATOR`. `LocatorResolver.Resolve` and `DeviceLocator.ResolveExpression` resolve the union. Differences: the parameter is typed, so upstream's refusal of a non-locator has no port (`null` is an `ArgumentNullException`); and the port's web engine resolves locators in core and has no frame locators or Playwright projection, so upstream's `UNSUPPORTED_CAPABILITY` for web branches with different frame-selector chains, or a display-value branch, has no port +- Screen queries: role, text, label, test id, placeholder, display value, `filter` (`hasText` and `has`), `or`, `first`, `last`, `nth`. A locator offers every query kind, scoped to its matches' descendants. Every query takes a `TextMatch`: a string or a .NET `Regex`, tested against the whitespace-normalized text. A string matches the whole text, case-sensitively, unless `exact` is false, which makes it a case-insensitive substring, as upstream. `RoleOptions` carries `name`, `checked`, `disabled`, `selected`, `expanded`, `pressed`, and `level`, and `img` is read as `image`. A role query never matches a hidden node; the other kinds keep hidden nodes unless `Visible` is set, as upstream. `getByLabel` matches any node whose accessible name matches, as the upstream reference resolver does. Options are C# objects (`RoleOptions`, `TextMatchOptions`) instead of object literals, and `filter({ hasText, has })` is the two overloads `Filter(TextMatch)` and `Filter(Locator)`. Regular expressions use .NET syntax, not ECMAScript. `GetByTestId` also takes `Exact`, which upstream's `getByTestId` does not. `locator.or(other)` (upstream #1049) is `Or(Locator)`: a `union` expression that matches either branch once, in document order, and stays strict (`STRICT_MODE` where upstream says `LOCATOR_AMBIGUOUS`; add `First()` when both may match). Both locators must come from the same screen and attempt, else `INVALID_LOCATOR`. `LocatorResolver.Resolve` and `DeviceLocator.ResolveExpression` resolve the union. Differences: the parameter is typed, so upstream's refusal of a non-locator has no port (`null` is an `ArgumentNullException`); and upstream's `UNSUPPORTED_CAPABILITY` for a display-value branch has no port, since the port's engines resolve over their tree. Web branches with different frame-selector chains fail with `UNSUPPORTED_CAPABILITY` (`locator.or requires both branches in the same document`) as upstream's `frameSelectors` does - Actions: tap, click (an alias of tap), doubleTap (`DblClickAsync` is an alias), fill (a string or a `Secret`), pressSequentially, press, check, uncheck, clear, selectOption, focus, scrollIntoView. They wait up to the action timeout for exactly one enabled, visible match. `selectOption` takes a string that matches an option value or label. `ActionOptions.Timeout` overrides the wait per action (`PressSequentiallyOptions` adds `Delay`). The web engine's own Playwright wait still uses the configured action timeout - Locator reads: `textContent`, `inputValue`, `getAttribute`, `isVisible`, `isHidden`, `isEnabled`, `isDisabled`, `isChecked`, `boundingBox`, `count`, `all`, `allTextContents`. A read of one node (`textContent`, `inputValue`, `getAttribute`, `isEnabled`, `isDisabled`, `isChecked`, `boundingBox`) waits for its one match until the action timeout, then reads once with no retry for a value; `isVisible`, `isHidden`, `count`, `all`, and `allTextContents` answer from the current screen at once. No read verifies an earlier `act`. `textContent` and `allTextContents` collapse each whitespace run to one space and trim, as upstream. A single-node read fails with `NOT_FOUND` when nothing matches before the action timeout ends -- `screen.tree()` and `screen.debug()` (commit `d7ec0ec`) are `Screen.TreeAsync` and `Screen.DebugAsync`. One observation of the whole screen, rendered by `FailureEvidence.Listing` (the trace's screen at failure, upstream's `prepareObservation`): one node per line, children indented, secrets redacted, cut at the observation byte limit (262144). A node the page does not show is left out. A step of kind `screen`. `DebugAsync` writes the text to `Console.Out`, where a test framework picks it up. Differences: `Timeout` bounds the read with a linked token and fails with `OPERATION_TIMEOUT`, where upstream passes it to the engine operation. A `Locator` has no `TreeAsync`, so upstream's refusal on a locator is a compile error, and the port has no frames. The same commit's change to print test output without the `list` reporter, and to keep stdout for `--reporter json`, is not ported: the port has no runner. `screen-tree.test.ts` is `ScreenTree/ScreenTreeTests` and `ScreenDebugTests` (not ported: the refusal on a locator, the unknown option, and `sdk-types.ts`'s checks, which a typed API makes compile errors); `screen-tree-secrets.test.ts` is `ScreenTreeSecrets/ScreenTreeTests` (through a session over a fake engine). `run-user-output.test.ts`'s two new cases are not ported with the runner change. +- `screen.tree()` and `screen.debug()` (commit `d7ec0ec`) are `Screen.TreeAsync` and `Screen.DebugAsync`. One observation of the whole screen, rendered by `FailureEvidence.Listing` (the trace's screen at failure, upstream's `prepareObservation`): one node per line, children indented, secrets redacted, cut at the observation byte limit (262144). A node the page does not show is left out. A step of kind `screen`. `DebugAsync` writes the text to `Console.Out`, where a test framework picks it up. Differences: `Timeout` bounds the read with a linked token and fails with `OPERATION_TIMEOUT`, where upstream passes it to the engine operation. A `Locator` has no `TreeAsync`, so upstream's refusal on a locator is a compile error. On a `FrameScreen` they refuse with `INVALID_ARGUMENT`, as upstream's do. The same commit's change to print test output without the `list` reporter, and to keep stdout for `--reporter json`, is not ported: the port has no runner. `screen-tree.test.ts` is `ScreenTree/ScreenTreeTests` and `ScreenDebugTests` (not ported: the refusal on a locator, the unknown option, and `sdk-types.ts`'s checks, which a typed API makes compile errors); `screen-tree-secrets.test.ts` is `ScreenTreeSecrets/ScreenTreeTests` (through a session over a fake engine). `run-user-output.test.ts`'s two new cases are not ported with the runner change. - `screen.scrollUntilVisible` and `locator.scrollUntilVisible` (`locator/screen.ts`) are `Screen.ScrollUntilVisibleAsync` and `Locator.ScrollUntilVisibleAsync`, with `ScrollUntilVisibleOptions { Direction, Momentum, Timeout }` (down, slow, 30 s by default). `Momentum` (`None`, `Slow`, `Fast`; upstream's `Momentum`) moved from `E2E.Mobile` to `E2E.Engine` beside `ScrollDirection`, and `LocatorAction.Swipe` carries it. `IEngineSession.SwipeAsync(direction, momentum, ct)` is the viewport swipe with a stride; its default drops the momentum. `WebEngine` strides as upstream's `swipeDistance` (none half, slow three quarters, fast one and a half of the extent; a swipe naming none keeps three quarters), `MobileEngine` strides a node swipe by it and keeps the device client's default for the whole-screen swipe, and `DocumentEngine` and `WinAppEngine` have one stride. The port has no operation context, so a swipe's deadline is the cancellation of the token it runs under, and a swipe the deadline ends fails with an `OPERATION_TIMEOUT` cause. A locator swipe's engine error is not wrapped in `ACTION_FAILED`, since the port has no `translateLocatorError`. `filter({ has })` now refuses a locator of another screen with `INVALID_LOCATOR`, as upstream's `ownLocator` does - `locator.waitFor` with `attached`, `detached`, `visible` (default), and `hidden`, as `WaitForAsync(new LocatorWaitForOptions { State, Timeout })`. The timeout defaults to the action timeout. `LocatorWaitForOptions` is named apart from `agent.waitFor`'s `WaitForOptions`. Differences: a timeout fails with `TIMEOUT` (upstream `LOCATOR_NOT_FOUND`, `locator did not become `), and a passing `waitFor` does not verify an earlier `act` (upstream it does). The timeout carries `Details` as upstream's does (the locator, what it asked for, and how long it waited; see [Trace page](#trace-page)) - Locator expectations: visible, hidden, attached, enabled, disabled, checked, selected, expanded, focused, text, contained text, value, attribute, accessible name, and count. They poll until the assertion timeout, or the matcher's own `timeout`. `Not` inverts a matcher, which then passes after 1000 ms of continuous truth (or the whole budget when it is shorter). Text matchers take the same `TextMatch` as queries, `ignoreCase`, and a list form. The boolean flags are `visible`, `attached`, `enabled`, and `isChecked` (`checked` is a C# keyword) - `toHaveScreenshot` as `Expect.That(Screen).ToHaveScreenshotAsync(name, options)` and the same on a locator, with `Not` and `Expect.Soft(locator)`; see [Visual comparison](#visual-comparison) - `expect.soft` for every locator expectation, as `Expect.Soft(locator)`, including `.Not` and the per-matcher `timeout`. An `ASSERTION_FAILED` is kept and the body runs on; any other error still throws. `E2ETest` records each one on the NUnit result, as inside `Assert.EnterMultipleScope`, so the test fails when the body ends and lists every failure. `E2E.XUnit.V3.E2ETest` keeps them on the session and fails the test when it is disposed. As upstream, a failed soft assertion outranks a skip: `Assert.Ignore` or `Assert.Inconclusive` after one fails the test in NUnit, and the message keeps the skip reason beside the failures. In xUnit v3, `Assert.Skip` after one fails the test the same way. Other hosts set `E2ESessionOptions.OnSoftFailure` or call `E2ESession.CloseSoftFailures` -- `expect(browser)` as `Expect.That(Browser)`: `ToHaveURLAsync` (a string or a `Regex`, with `ignoreCase` and `timeout`) and `ToHaveTitleAsync` (a `TextMatch`, with `timeout`), `.Not`, and `Expect.Soft(Browser)`. Like the locator matchers, a passing one verifies the previous `act`, and `.Not` passes after 1000 ms of the opposite. A read that outlasts the matcher's deadline ends it, so a page that stops answering fails at the assertion timeout +- `expect(browser)` as `Expect.That(Browser)`: `ToHaveURLAsync` (a string or a `Regex`, with `ignoreCase` and `timeout`), `ToHaveTitleAsync` (a `TextMatch`, with `timeout`), and `ToHaveClassAsync(locator, TextMatch)`, `.Not`, and `Expect.Soft(Browser)`. Like the locator matchers, a passing one verifies the previous `act`, and `.Not` passes after 1000 ms of the opposite. A read that outlasts the matcher's deadline ends it, so a page that stops answering fails at the assertion timeout - `expect.poll` as `Expect.Poll(read, options)`, with `Timeout`, `Interval`, and `Message`. A read that throws is retried. The matchers are `ToBeAsync`, `ToSatisfyAsync`, and `.Not`; `E2E.NUnit` adds `ToMatchAsync` for any NUnit constraint, such as `Is.GreaterThan(3)`. The default timeout is 5 seconds, not the configured assertion timeout, because the poll does not see the running test, and it does not stop at the test deadline unless a cancellation token is passed - `agent.act`, `agent.assert`, `agent.waitFor`, `agent.extract`. `ActOptions` has `Params`, `Timeout`, `MaxSteps`, `MaxModelCalls`, and `Agent`; `AssertOptions` has `Timeout` and `Agent`; `WaitForOptions` has `Timeout`, `Interval`, `MaxModelCalls`, and `Agent`; `ExtractOptions` has `Timeout` and `Agent`. See [Agents](#agents) - Act tools `observe`, `scroll`, `scroll_to`, and `back`. They are offered when the engine declares `EngineCapabilities.Scroll` or `EngineCapabilities.History`. `scroll_to` with a target scrolls it into view. With text, it pages the viewport, or the target list, until a node reading the text is listed, then scrolls it into view. It stops when the screen stops moving @@ -84,7 +84,7 @@ Names are C# versions of the JavaScript API: `agent.act` is `ActAsync`, `screen. - The test file is the file the call is compiled in (`[CallerFilePath]`), not a runner's test entry. A call in a helper stores beside the helper. A build that rewrites source paths (`DeterministicSourcePaths`) breaks it. - The update flag is `E2E_UPDATE_SNAPSHOTS=1`, or the fixture property `UpdateSnapshots`, in place of `--update-snapshots` (`-u`). There is no CLI flag or config key. - Failure images go to `.e2e/results//attempt-/` (the fixture property `ResultsDirectory` moves it). They are not attached to a step, because the port has no step report. A screenshot written earlier in the run is tracked per process, not per run directory. -- `Expect.That(screen)` is the only screen matcher. There is no `Expect.Soft(screen)`, and no scoped screen to refuse (the port has no `frameLocator`). +- `Expect.That(screen)` is the only screen matcher. There is no `Expect.Soft(screen)`. `Expect.That(frameScreen).ToHaveScreenshotAsync` (the scope `Browser.FrameLocator` returns) fails with `INVALID_ARGUMENT`, as upstream's does, and `TreeAsync` and `DebugAsync` on it do too (`screen.tree reads the whole screen; call it on screen`). - Secure fields are covered by the engine (Playwright masks, in every frame, including a field inside a closed shadow root, which the engine reaches through the `e2e-closed=` selector engine it registers with Playwright before it opens a browser) and by painting the observed secure nodes. A closed root made by declarative shadow DOM (`