diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a8aead9c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Keep test fixture bodies byte-stable across platforms (Windows checkout must not inject CRLF). +src/PlaywrightNative.TestServer/wwwroot/** text eol=lf +*.json text eol=lf diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index fdc79d9d..ffd99c2f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -15,6 +15,10 @@ on: - '**.csproj' - '**.runsettings' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: DOTNET_VERSION: '10.0.x' # Pin BrowserFetcher's cache to a job-local path so actions/cache can @@ -23,7 +27,7 @@ env: jobs: build: - name: ${{ matrix.browser }}-${{ matrix.mode }}-${{ matrix.os }} + name: ${{ matrix.browser }}-${{ matrix.mode }}-${{ matrix.os }}${{ matrix.shardTotal && format('-shard{0}of{1}', matrix.shardIndex, matrix.shardTotal) || '' }} runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -32,23 +36,107 @@ jobs: # Chromium — full coverage on Linux + Windows, headless + headful. - { os: ubuntu-latest, browser: chromium, mode: headless } - { os: ubuntu-latest, browser: chromium, mode: headful } - - { os: windows-latest, browser: chromium, mode: headless } - - { os: windows-latest, browser: chromium, mode: headful } + # Windows runs much slower per-test than Linux and never reaches + # the end of the suite before the org's CI timeout kills the job + # (observed: ~40% done at the ~2h cutoff). Split into shards by + # test class so each one finishes with headroom to spare. + - { os: windows-latest, browser: chromium, mode: headless, shardIndex: 1, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headless, shardIndex: 2, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headless, shardIndex: 3, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headless, shardIndex: 4, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headful, shardIndex: 1, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headful, shardIndex: 2, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headful, shardIndex: 3, shardTotal: 4 } + - { os: windows-latest, browser: chromium, mode: headful, shardIndex: 4, shardTotal: 4 } # WebKit — full test suite against the protocol stack. # macOS + Linux headless. Expect failures until WebKit catches up. - - { os: macos-14, browser: webkit, mode: headless } + # macOS also hits the CI timeout even at a 3-way split (observed: + # one shard still ~92% done at cutoff -- it draws a disproportionate + # cluster of client-cert/TLS tests that hang ~30s each on macOS's + # Kestrel TLS 1.3 limitation). 5-way spreads that load thinner. + - { os: macos-14, browser: webkit, mode: headless, shardIndex: 1, shardTotal: 5 } + - { os: macos-14, browser: webkit, mode: headless, shardIndex: 2, shardTotal: 5 } + - { os: macos-14, browser: webkit, mode: headless, shardIndex: 3, shardTotal: 5 } + - { os: macos-14, browser: webkit, mode: headless, shardIndex: 4, shardTotal: 5 } + - { os: macos-14, browser: webkit, mode: headless, shardIndex: 5, shardTotal: 5 } - { os: ubuntu-latest, browser: webkit, mode: headless } steps: - uses: actions/checkout@v4 + # builds.dotnet.microsoft.com occasionally fails to serve the Windows SDK + # zip (transient CDN / network). Retry a few times before failing the job. - name: Setup .NET + id: setup-dotnet-1 + continue-on-error: true + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Wait before Setup .NET retry + if: steps.setup-dotnet-1.outcome == 'failure' + shell: bash + run: sleep 20 + - name: Setup .NET (retry 2) + id: setup-dotnet-2 + if: steps.setup-dotnet-1.outcome == 'failure' + continue-on-error: true + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Wait before Setup .NET final retry + if: steps.setup-dotnet-1.outcome == 'failure' && steps.setup-dotnet-2.outcome == 'failure' + shell: bash + run: sleep 40 + - name: Setup .NET (retry 3) + if: steps.setup-dotnet-1.outcome == 'failure' && steps.setup-dotnet-2.outcome == 'failure' uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Install ffmpeg (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + # Bypass Chocolatey — community.chocolatey.org regularly 504s on the + # ffmpeg package and can still exit 0, which made retries ineffective. + $ErrorActionPreference = 'Stop' + $zip = Join-Path $env:RUNNER_TEMP 'ffmpeg.zip' + $dir = Join-Path $env:RUNNER_TEMP 'ffmpeg' + $url = 'https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip' + New-Item -ItemType Directory -Force -Path $dir | Out-Null + $downloaded = $false + for ($attempt = 1; $attempt -le 5; $attempt++) { + Write-Host "Downloading ffmpeg (attempt $attempt/5) from $url" + try { + Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing + $downloaded = $true + break + } catch { + Write-Warning "Download failed: $($_.Exception.Message)" + if ($attempt -eq 5) { throw } + Start-Sleep -Seconds (15 * $attempt) + } + } + if (-not $downloaded) { throw 'ffmpeg download failed' } + Expand-Archive -Path $zip -DestinationPath $dir -Force + $bin = Get-ChildItem -Path $dir -Recurse -Filter 'ffmpeg.exe' | Select-Object -First 1 + if (-not $bin) { throw 'ffmpeg.exe not found after extract' } + Add-Content -Path $env:GITHUB_PATH -Value $bin.Directory.FullName + & $bin.FullName -version + $probe = Join-Path $bin.Directory.FullName 'ffprobe.exe' + if (Test-Path $probe) { & $probe -version } + - name: Install ffmpeg (macOS) + if: startsWith(matrix.os, 'macos') + run: | + brew install ffmpeg + # Recent Homebrew ffmpeg bottles omit --enable-libwebp; cwebp covers + # screenshot WebP recoding when ffmpeg lacks that encoder. + brew install webp + ffmpeg -version + ffprobe -version + cwebp -version - name: Install system dependencies (Linux, Chromium) if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chromium' run: | sudo apt-get update - sudo apt-get install -y libgbm-dev xvfb + sudo apt-get install -y libgbm-dev xvfb ffmpeg webp - name: Install system dependencies (Linux, WebKit) if: matrix.os == 'ubuntu-latest' && matrix.browser == 'webkit' run: | @@ -62,6 +150,8 @@ jobs: # missing package means the browser will crash at runtime with an # obscure dynamic-loader error. sudo apt-get install -y \ + ffmpeg \ + webp \ gstreamer1.0-libav gstreamer1.0-plugins-bad gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ libatk-bridge2.0-0t64 libatk1.0-0t64 libatomic1 libavif16 libcairo-gobject2 libcairo2 \ libdbus-1-3 libdrm2 libenchant-2-2 libepoxy0 libevent-2.1-7t64 libflite1 \ @@ -84,18 +174,37 @@ jobs: # No restore-keys fallback: we want a stale cache to be a clean miss # rather than a partial restore that masks broken binaries. - name: Create HTTPS certificate (Linux) - if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chromium' + if: matrix.os == 'ubuntu-latest' run: | mkdir -p src/PlaywrightNative.TestServer dotnet dev-certs https --clean + # PKCS12 with private key for Kestrel. Public DER alone makes the + # handshake abort (ERR_CONNECTION_CLOSED / TLS EOF). + dotnet dev-certs https -ep src/PlaywrightNative.TestServer/key.pfx -p playwright dotnet dev-certs https -ep src/PlaywrightNative.TestServer/testCert.cer - sudo openssl x509 -inform der -in src/PlaywrightNative.TestServer/testCert.cer -out /usr/local/share/ca-certificates/testCert.crt -outform pem - sudo update-ca-certificates + # Do NOT install testCert into the system CA store. WebKit/libsoup + # trusts OS CAs, so a system-trusted "bad SSL" cert makes page.goto + # succeed and breaks ShouldFailWhenNavigatingToBadSsl / + # ignoreHTTPSErrors isolation (caughtException null). Chromium keeps + # its own root store and was unaffected. Tests that need HTTPS to + # succeed must use IgnoreHTTPSErrors (upstream parity). - name: Create HTTPS certificate (Windows) if: matrix.os == 'windows-latest' shell: pwsh run: | dotnet dev-certs https --clean + # Export PKCS12 so SimpleServer.LoadHttpsCertificate can feed Kestrel a + # private key. Public-only testCert.cer / PEM rematerialization still + # yields ERR_CONNECTION_CLOSED on Windows runners. + New-Item -ItemType Directory -Force -Path src/PlaywrightNative.TestServer | Out-Null + dotnet dev-certs https -ep src/PlaywrightNative.TestServer/key.pfx -p playwright + dotnet dev-certs https -ep src/PlaywrightNative.TestServer/testCert.cer + - name: Create HTTPS certificate (macOS) + if: startsWith(matrix.os, 'macos') + run: | + mkdir -p src/PlaywrightNative.TestServer + dotnet dev-certs https --clean + dotnet dev-certs https -ep src/PlaywrightNative.TestServer/key.pfx -p playwright dotnet dev-certs https -ep src/PlaywrightNative.TestServer/testCert.cer - name: Check formatting if: ${{ matrix.os == 'ubuntu-latest' && matrix.browser == 'chromium' && matrix.mode == 'headless' }} @@ -106,7 +215,36 @@ jobs: chmod +x ./.github/workflows/no-puppeteer.sh ./.github/workflows/no-puppeteer.sh - name: Build - run: dotnet build ./src/PlaywrightNative.sln + run: dotnet build ./src/PlaywrightNative.sln -c Release + - name: Compute test shard filter + if: matrix.shardTotal + shell: bash + env: + # Git Bash's MSYS layer auto-converts any argument that looks like a + # POSIX path (e.g. "/ListFullyQualifiedTests") into a bogus Windows + # path before exec'ing a native tool. Disable that for this step. + MSYS_NO_PATHCONV: "1" + run: | + # List every test's fully-qualified name straight from the built + # assembly (dotnet test --list-tests only prints the bare method + # name, which collides across classes and isn't safe to filter on). + # $RUNNER_TEMP (not /tmp) so the path is already OS-native on Windows. + all_tests="$RUNNER_TEMP/all_tests.txt" + dotnet vstest src/PlaywrightNative.Tests/bin/Release/net10.0/PlaywrightNative.Tests.dll \ + /ListFullyQualifiedTests "/ListTestsTargetPath:$all_tests" + # Drop the trailing ".MethodName" (and any TestCase "(...)" suffix) + # to get one entry per test class, then assign classes to shards by + # position in a stable sort -- deterministic across the shardTotal + # jobs of a single run without the shards needing to coordinate. + all_classes="$RUNNER_TEMP/all_classes.txt" + sed -E 's/\.[A-Za-z0-9_]+(\([^)]*\))?$//' "$all_tests" | sort -u > "$all_classes" + echo "Discovered $(wc -l < "$all_classes") test classes" + shard_classes="$RUNNER_TEMP/shard_classes.txt" + awk -v shard="${{ matrix.shardIndex }}" -v total="${{ matrix.shardTotal }}" \ + 'NR % total == (shard - 1) { print }' "$all_classes" > "$shard_classes" + echo "Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}: $(wc -l < "$shard_classes") classes" + filter=$(awk '{ printf "%sFullyQualifiedName~%s.", (NR > 1 ? "|" : ""), $0 }' "$shard_classes") + echo "TEST_FILTER=$filter" >> "$GITHUB_ENV" - name: Disable AppArmor (Linux) if: matrix.os == 'ubuntu-latest' run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns || true @@ -115,29 +253,37 @@ jobs: env: PRODUCT: CHROMIUM run: | - dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings + dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -c Release -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings - name: Test (Chromium, Linux headful) if: ${{ matrix.browser == 'chromium' && matrix.os == 'ubuntu-latest' && matrix.mode == 'headful' }} env: PRODUCT: CHROMIUM + HEADLESS: "false" run: | xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- \ - dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings + dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -c Release -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings - name: Test (Chromium, Windows) if: ${{ matrix.browser == 'chromium' && matrix.os == 'windows-latest' }} + shell: bash env: PRODUCT: CHROMIUM + HEADLESS: ${{ matrix.mode == 'headless' && 'true' || 'false' }} run: | - dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings + dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -c Release -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings ${TEST_FILTER:+--filter "$TEST_FILTER"} - name: Test (WebKit) if: matrix.browser == 'webkit' + shell: bash env: PRODUCT: WEBKIT run: | + # Raise the soft FD limit. Failed tests dispose+relaunch the worker + # browser; without enough headroom, macOS runners hit EMFILE mid-suite + # even after pipe/transport disposal fixes. + ulimit -n 65536 || true # Known-failing tests on WebKit are marked in PlaywrightNative.Nunit's # TestExpectations.local.json (browser+platform+mode aware). The JSON # currently covers WK feature gaps (ExposeFunction, AddInitScript, # AddScriptTag/AddStyleTag, EmulateMedia, PDF, screenshots, SetContent, # SetViewportSize) and the macOS-14 inner-target hang. Retire entries # as the underlying work lands. - dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings + dotnet test ./src/PlaywrightNative.Tests/PlaywrightNative.Tests.csproj --no-build -c Release -f net10.0 -s src/PlaywrightNative.Tests/test.runsettings ${TEST_FILTER:+--filter "$TEST_FILTER"} diff --git a/.gitignore b/.gitignore index d5e4219d..65e9d2be 100644 --- a/.gitignore +++ b/.gitignore @@ -227,6 +227,7 @@ ClientBin/ *.dbproj.schemaview *.jfm *.pfx +!src/PlaywrightNative.Tests/Assets/**/*.pfx *.publishsettings orleans.codegen.cs diff --git a/src/PlaywrightNative.NUnit/BrowserExecutable.cs b/src/PlaywrightNative.NUnit/BrowserExecutable.cs index 79b8233c..42996ccf 100644 --- a/src/PlaywrightNative.NUnit/BrowserExecutable.cs +++ b/src/PlaywrightNative.NUnit/BrowserExecutable.cs @@ -45,6 +45,7 @@ public static class BrowserExecutable private static bool _chromiumResolved; private static bool _webkitResolved; private static bool _firefoxResolved; + private static bool _ffmpegResolved; /// /// Gets the resolved Chromium executable path, or null when unavailable. @@ -62,12 +63,20 @@ public static class BrowserExecutable public static string FirefoxExecutablePath { get; private set; } /// - /// Ensures Chromium is resolved, and WebKit/Firefox when PRODUCT/BROWSER - /// selects them. + /// Gets the resolved ffmpeg executable path, or null when unavailable. + /// + public static string FfmpegExecutablePath { get; private set; } + + /// + /// Ensures Chromium and ffmpeg are resolved, and WebKit/Firefox when + /// PRODUCT/BROWSER selects them. ffmpeg is fetched unconditionally + /// (official installs it by default alongside every browser) so WebP/screencast + /// helpers can find it via regardless of product. /// public static async Task EnsureCurrentProductAsync() { await EnsureAsync("chromium").ConfigureAwait(false); + await EnsureAsync("ffmpeg").ConfigureAwait(false); string browserName = ResolveBrowserName(); if (browserName == "webkit") @@ -112,6 +121,16 @@ public static async Task EnsureAsync(string browserName) _webkitResolved = true; } + break; + case "ffmpeg": + if (!_ffmpegResolved) + { + FfmpegExecutablePath = await ResolveBrowserAsync( + SupportedBrowser.Ffmpeg, + "PLAYWRIGHT_FFMPEG_PATH").ConfigureAwait(false); + _ffmpegResolved = true; + } + break; default: if (!_chromiumResolved) @@ -170,7 +189,11 @@ public static async Task CreateLaunchOptionsAsync(stri Assert.Ignore($"{label} executable not available (download skipped or failed)."); } - return new BrowserTypeLaunchOptions { ExecutablePath = path }; + bool headless = !string.Equals( + Environment.GetEnvironmentVariable("HEADLESS"), + "false", + StringComparison.OrdinalIgnoreCase); + return new BrowserTypeLaunchOptions { ExecutablePath = path, Headless = headless }; } /// @@ -229,9 +252,12 @@ private static async Task ResolveBrowserAsync(SupportedBrowser browser, return downloadedPath; } } - catch + catch (Exception ex) { // Network unreachable, archive corrupt, or extraction failed. + // Surface the reason so CI logs explain skipped browser tests. + TestContext.Progress.WriteLine( + $"BrowserExecutable: failed to download {browser}: {ex.Message}"); } return null; @@ -260,6 +286,11 @@ public class BrowserExecutableFixture /// public static string FirefoxExecutablePath => BrowserExecutable.FirefoxExecutablePath; + /// + /// Gets . + /// + public static string FfmpegExecutablePath => BrowserExecutable.FfmpegExecutablePath; + /// /// Prefetches browsers for the current product. /// diff --git a/src/PlaywrightNative.NUnit/BrowserService.cs b/src/PlaywrightNative.NUnit/BrowserService.cs index fdc81d40..6bcd29ee 100644 --- a/src/PlaywrightNative.NUnit/BrowserService.cs +++ b/src/PlaywrightNative.NUnit/BrowserService.cs @@ -58,5 +58,17 @@ private static async Task CreateBrowserAsync(string browserName, Brows public Task ResetAsync() => Task.CompletedTask; - public Task DisposeAsync() => Browser.CloseAsync(); + public async Task DisposeAsync() + { + // Prefer full disposal so pipe transports / process handles are released. + // CloseAsync alone historically left AnonymousPipeServerStream FDs open, + // and WorkerAwareTest disposes the browser after every failed test. + if (Browser is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + return; + } + + await Browser.CloseAsync().ConfigureAwait(false); + } } diff --git a/src/PlaywrightNative.NUnit/BrowserTest.cs b/src/PlaywrightNative.NUnit/BrowserTest.cs index bf726b51..2b84ac13 100644 --- a/src/PlaywrightNative.NUnit/BrowserTest.cs +++ b/src/PlaywrightNative.NUnit/BrowserTest.cs @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Playwright; @@ -36,7 +37,7 @@ public class BrowserTest : PlaywrightTest public IBrowser Browser { get; private set; } = null!; /// - /// Creates a new context and tracks it for tear-down when the test passes. + /// Creates a new context and tracks it for tear-down. /// /// Optional context options. /// The new browser context. @@ -61,20 +62,29 @@ public async Task BrowserSetup() } /// - /// Closes contexts created via when the test passed. + /// Closes contexts created via . + /// Always closes — even on failure — so contexts do not linger on a reused + /// browser, and so failed tests do not rely solely on browser dispose for FD cleanup. /// [TearDown] public async Task BrowserTearDown() { - if (TestOk()) + // Snapshot first: CloseAsync can fire events that create/track more contexts + // and would otherwise throw Collection was modified during enumeration. + IBrowserContext[] contexts = _contexts.ToArray(); + _contexts.Clear(); + foreach (IBrowserContext context in contexts) { - foreach (IBrowserContext context in _contexts) + try { await context.CloseAsync().ConfigureAwait(false); } + catch (Exception) + { + // Best-effort cleanup during teardown. + } } - _contexts.Clear(); Browser = null!; } diff --git a/src/PlaywrightNative.NUnit/TestExpectations/TestExpectations.local.json b/src/PlaywrightNative.NUnit/TestExpectations/TestExpectations.local.json index 485c1c93..a28c1ed3 100644 --- a/src/PlaywrightNative.NUnit/TestExpectations/TestExpectations.local.json +++ b/src/PlaywrightNative.NUnit/TestExpectations/TestExpectations.local.json @@ -374,7 +374,7 @@ "expectations": ["FAIL"] }, { - "testIdPattern": "[direct/page-content-and-media.cs] PdfAsyncShouldReturnPdfBytes", + "testIdPattern": "[page-set-content.spec.ts] PdfAsyncShouldReturnPdfBytes", "platforms": ["darwin", "linux", "win32"], "parameters": ["webkit"], "expectations": ["SKIP"], diff --git a/src/PlaywrightNative.TestServer/OfficialServerWebSocket.cs b/src/PlaywrightNative.TestServer/OfficialServerWebSocket.cs index 37fb861f..7ed7fcac 100644 --- a/src/PlaywrightNative.TestServer/OfficialServerWebSocket.cs +++ b/src/PlaywrightNative.TestServer/OfficialServerWebSocket.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net.Sockets; using System.Net.WebSockets; using System.Text; using System.Threading; @@ -24,19 +25,36 @@ public sealed class OfficialServerWebSocket private readonly List _bufferedMessages = new List(); private bool _receiveStarted; private bool _closed; + private bool _closeSent; private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + private readonly TaskCompletionSource _closedTcs = + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); internal OfficialServerWebSocket(WebSocket socket, Stream stream = null) { _socket = socket; _stream = stream; + + // When we own the raw upgraded stream, start the receive loop immediately so + // ping/close frames are handled even before the test registers listeners. + // Deferred start raced macOS WebKit client closes (page saw 1006). + if (_stream != null) + { + EnsureReceive(); + } } internal OfficialServerWebSocket(Stream stream) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + EnsureReceive(); } + /// + /// Completes when the peer close handshake finishes or the stream drops. + /// + internal Task WaitUntilClosedAsync() => _closedTcs.Task; + /// /// Registers a one-shot text-or-binary message listener. Binary frames /// are decoded as UTF-8, matching Node data.toString(). @@ -96,13 +114,20 @@ public void OnceClose(Action handler) public void Send(string text) { byte[] bytes = Encoding.UTF8.GetBytes(text ?? string.Empty); - if (_socket != null) + + // Prefer the upgraded stream when present. ManagedWebSocket shares that + // stream; mixing APIs corrupts framing, and CloseAsync has failed to echo + // application close codes (3000–4999) on macOS WebKit (page sees 1006). + if (_stream != null) { - _ = _socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None); + WriteFrame(opcode: 1, bytes); return; } - WriteFrame(opcode: 1, bytes); + if (_socket != null) + { + _ = _socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None); + } } /// @@ -112,13 +137,16 @@ public void Send(string text) public void Send(byte[] payload) { byte[] bytes = payload ?? Array.Empty(); - if (_socket != null) + if (_stream != null) { - _ = _socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, true, CancellationToken.None); + WriteFrame(opcode: 2, bytes); return; } - WriteFrame(opcode: 2, bytes); + if (_socket != null) + { + _ = _socket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, true, CancellationToken.None); + } } /// @@ -129,30 +157,32 @@ public void Send(byte[] payload) public void Close(int code, string reason) { string text = reason ?? string.Empty; + if (_stream != null) + { + byte[] reasonBytes = Encoding.UTF8.GetBytes(text); + byte[] payload = new byte[2 + reasonBytes.Length]; + payload[0] = (byte)((code >> 8) & 0xFF); + payload[1] = (byte)(code & 0xFF); + Buffer.BlockCopy(reasonBytes, 0, payload, 2, reasonBytes.Length); + // Mark before write so a concurrent peer close does not echo a + // second close frame (Chromium then surfaces error+1006). + _closeSent = true; + WriteFrame(opcode: 8, payload); + return; + } + if (_socket != null) { - WebSocketCloseStatus status = Enum.IsDefined(typeof(WebSocketCloseStatus), code) - ? (WebSocketCloseStatus)code - : WebSocketCloseStatus.NormalClosure; try { - status = (WebSocketCloseStatus)code; + WebSocketCloseStatus status = (WebSocketCloseStatus)code; _ = _socket.CloseAsync(status, text, CancellationToken.None); } catch (ArgumentException) { _ = _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, text, CancellationToken.None); } - - return; } - - byte[] reasonBytes = Encoding.UTF8.GetBytes(text); - byte[] payload = new byte[2 + reasonBytes.Length]; - payload[0] = (byte)((code >> 8) & 0xFF); - payload[1] = (byte)(code & 0xFF); - Buffer.BlockCopy(reasonBytes, 0, payload, 2, reasonBytes.Length); - WriteFrame(opcode: 8, payload); } /// @@ -222,15 +252,19 @@ private async Task ReceiveLoopAsync() { try { - if (_socket != null) + // Prefer raw frames when the upgraded stream is available so client + // close codes (e.g. 3002) are echoed byte-for-byte. ManagedWebSocket + // CloseAsync can fail to complete the handshake for application codes + // on some platforms; WebKit then reports error + close 1006. + if (_stream != null) { - await ReceiveSocketAsync().ConfigureAwait(false); + await ReceiveStreamAsync().ConfigureAwait(false); return; } - if (_stream != null) + if (_socket != null) { - await ReceiveStreamAsync().ConfigureAwait(false); + await ReceiveSocketAsync().ConfigureAwait(false); } } catch (IOException) @@ -259,18 +293,33 @@ private async Task ReceiveSocketAsync() { int code = result.CloseStatus.HasValue ? (int)result.CloseStatus.Value : 1005; byte[] reason = Encoding.UTF8.GetBytes(result.CloseStatusDescription ?? string.Empty); + WebSocketCloseStatus status = result.CloseStatus ?? WebSocketCloseStatus.NormalClosure; + string description = result.CloseStatusDescription ?? string.Empty; try { - await _socket.CloseAsync( - result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, - CancellationToken.None).ConfigureAwait(false); + // Already received the peer close frame — only send ours. + await _socket.CloseOutputAsync(status, description, CancellationToken.None) + .ConfigureAwait(false); } catch (WebSocketException) { } catch (ArgumentException) { + try + { + await _socket.CloseOutputAsync( + WebSocketCloseStatus.NormalClosure, + description, + CancellationToken.None) + .ConfigureAwait(false); + } + catch (WebSocketException) + { + } + catch (ArgumentException) + { + } } NotifyClose(code, reason); @@ -300,11 +349,87 @@ private async Task ReceiveStreamAsync() byte[] reason = payload.Length > 2 ? payload.AsSpan(2).ToArray() : Array.Empty(); - WriteFrame(opcode: 8, payload); + // RFC 6455: only reply with Close if we have not already sent one. + // Echoing after a server-initiated Close confuses Chromium into + // error + 1006 (ShouldWorkWithTextMessage). + if (!_closeSent) + { + _closeSent = true; + WriteFrame(opcode: 8, payload); + } + + // Let dual Darwin proxies (Mac bypass shim → LocaleHandshakeProxy) + // copy the close echo to CFNetwork BEFORE TCP FIN. Shutdown(Send) + // immediately after WriteFrame coalesced echo+FIN through both + // hops; WebKit then reported error + close 1006 instead of clean + // application close 3002 (ShouldWorkWithClientSideClose). + await Task.Delay(1600).ConfigureAwait(false); + + try + { + if (_stream is NetworkStream network) + { + try + { + network.Socket.LingerState = new LingerOption(true, 10); + network.Socket.NoDelay = true; + } + catch (SocketException) + { + } + + network.Socket?.Shutdown(SocketShutdown.Send); + } + } + catch (SocketException) + { + } + catch (ObjectDisposedException) + { + } + + // Brief drain so proxy hops observe EOF after the delayed FIN. + // Cap the wait — a long drain deadlocks when the peer still + // expects a clean close that never arrives. + try + { + byte[] sink = new byte[256]; + using CancellationTokenSource drainCts = + new CancellationTokenSource(TimeSpan.FromMilliseconds(800)); + while (true) + { + int n = await _stream.ReadAsync(sink.AsMemory(0, sink.Length), drainCts.Token) + .ConfigureAwait(false); + if (n == 0) + { + break; + } + } + } + catch (IOException) + { + } + catch (ObjectDisposedException) + { + } + catch (OperationCanceledException) + { + } + NotifyClose(code, reason); + // Keep the upgrade handler alive briefly so Kestrel does not + // dispose the stream while bypass/handshake shims still flush. + await Task.Delay(400).ConfigureAwait(false); return; } + if (opcode == 9) + { + // Respond to ping so the peer does not abort the connection. + WriteFrame(opcode: 10, payload); + continue; + } + if (opcode == 1 || opcode == 2) { NotifyMessage(Encoding.UTF8.GetString(payload)); @@ -487,6 +612,7 @@ private void NotifyClose(int code, byte[] reason) listeners = new List>(_closeListeners); } + _closedTcs.TrySetResult(true); handler?.Invoke(code, reason ?? Array.Empty()); foreach (Action listener in listeners) { diff --git a/src/PlaywrightNative.TestServer/SimpleServer.cs b/src/PlaywrightNative.TestServer/SimpleServer.cs index c89d35f8..8f0735e5 100644 --- a/src/PlaywrightNative.TestServer/SimpleServer.cs +++ b/src/PlaywrightNative.TestServer/SimpleServer.cs @@ -5,7 +5,9 @@ using System.Linq; using System.Net; using System.Net.WebSockets; +using System.Security.Authentication; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,8 +15,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; namespace PlaywrightNative.TestServer { @@ -24,6 +29,7 @@ public class SimpleServer private readonly IDictionary> _subscribers; private readonly IDictionary> _requestWaits; + private readonly ConcurrentDictionary _arrivedRequestCounts; private readonly IDictionary _routes; private readonly IDictionary _auths; private readonly IDictionary _csp; @@ -53,6 +59,7 @@ public SimpleServer(int port, string contentRoot, bool isHttps) { _subscribers = new ConcurrentDictionary>(); _requestWaits = new ConcurrentDictionary>(); + _arrivedRequestCounts = new ConcurrentDictionary(StringComparer.Ordinal); _routes = new ConcurrentDictionary(); _auths = new ConcurrentDictionary(); _csp = new ConcurrentDictionary(); @@ -167,8 +174,28 @@ public SimpleServer(int port, string contentRoot, bool isHttps) } if (TryGetRequestWait(context, out var requestWait)) { + // Defer waiter until after the route handler's sync preamble + // (RequestAborted.Register) so Abort/RST cannot race registration + // (ShouldAbortRequestsWhenBrowserContextCloses on Windows). + string deferredRouteKey = (context.Request.Path.Value ?? string.Empty) + + (context.Request.QueryString.HasValue ? context.Request.QueryString.Value : string.Empty); + if (_routes.TryGetValue(deferredRouteKey, out var deferredHandler) + || _routes.TryGetValue(context.Request.Path.Value ?? string.Empty, out deferredHandler)) + { + Task handlerTask = deferredHandler(context); + requestWait(context); + await handlerTask.ConfigureAwait(false); + return; + } + requestWait(context); } + else + { + // No waiter yet — buffer so a racing WaitForRequest after + // frame.GoToAsync still observes this arrival. + RecordArrivedRequest(context); + } string routeKey = (context.Request.Path.Value ?? string.Empty) + (context.Request.QueryString.HasValue ? context.Request.QueryString.Value : string.Empty); if (_routes.TryGetValue(routeKey, out var handler) @@ -265,11 +292,41 @@ public SimpleServer(int port, string contentRoot, bool isHttps) if (!string.IsNullOrEmpty(certificatePath)) { string certificatePassword = Environment.GetEnvironmentVariable("PLAYWRIGHT_TEST_CERT_PASSWORD"); - listenOptions.UseHttps(Path.GetFullPath(certificatePath), certificatePassword); + X509Certificate2 certificate = LoadHttpsCertificate(certificatePath, certificatePassword); + + // Prefer TLS 1.3 (HAR/securityDetails assert it) but + // also offer 1.2. Kestrel SslProtocols.Tls13 alone + // fails the handshake on WebKit/mac ("An SSL error + // has occurred"), which breaks every HTTPS cookie + // third-party parity test. With both versions + // offered, capable clients still negotiate 1.3; + // WebKit/mac that cannot complete Kestrel's TLS 1.3 + // falls back to 1.2 (and empty securityConnection + // protocol still defaults to TLS 1.3 for HAR). + // Keep HTTP/1.1-only to avoid h2 ALPN quirks. + listenOptions.Protocols = HttpProtocols.Http1; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions + { + ServerCertificate = certificate, + SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13, + }); } else { - listenOptions.UseHttps("testCert.cer"); + // Prefer the tracked PEM fixtures / sibling PFX over + // Kestrel's UseHttps("testCert.cer"), which breaks on + // CI when only a public DER from `dotnet dev-certs` + // is present (no private key → TLS EOF). + string defaultCer = Path.Combine(contentRoot, "testCert.cer"); + X509Certificate2 fallback = LoadHttpsCertificate( + File.Exists(defaultCer) ? defaultCer : contentRoot, + certificatePassword: "playwright"); + listenOptions.Protocols = HttpProtocols.Http1; + listenOptions.UseHttps(new HttpsConnectionAdapterOptions + { + ServerCertificate = fallback, + SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13, + }); } }); } @@ -286,6 +343,76 @@ public SimpleServer(int port, string contentRoot, bool isHttps) public void SetCSP(string path, string csp) => _csp.Add(path, csp); + /// + /// Loads a TLS server certificate. CI exports key.pfx (password + /// playwright) via dotnet dev-certs https -ep. A public-only + /// DER testCert.cer cannot terminate TLS (browsers see + /// ERR_CONNECTION_CLOSED). Prefer PKCS12; rematerialize PEM into a + /// password-protected PKCS12 with + /// on Windows so Kestrel/SslStream can use the key. + /// + /// Path from PLAYWRIGHT_TEST_CERT_PATH. + /// Optional PKCS12 password. + /// A certificate with a private key suitable for Kestrel HTTPS. + private static X509Certificate2 LoadHttpsCertificate(string certificatePath, string certificatePassword) + { + string fullPath = Path.GetFullPath(certificatePath); + string extension = Path.GetExtension(fullPath); + string pfxPassword = string.IsNullOrEmpty(certificatePassword) + ? "playwright" + : certificatePassword; + + // File-based PKCS12: Exportable is enough. EphemeralKeySet is reserved + // for in-memory PEM rematerialization on Windows (macOS/Linux reject it). + X509KeyStorageFlags fileFlags = X509KeyStorageFlags.Exportable; + if (extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".p12", StringComparison.OrdinalIgnoreCase)) + { + return X509CertificateLoader.LoadPkcs12FromFile( + fullPath, + certificatePassword ?? pfxPassword, + fileFlags); + } + + string directory = Directory.Exists(fullPath) + ? fullPath + : (Path.GetDirectoryName(fullPath) ?? "."); + string siblingPfx = Path.Combine(directory, "key.pfx"); + if (File.Exists(siblingPfx)) + { + return X509CertificateLoader.LoadPkcs12FromFile(siblingPfx, pfxPassword, fileFlags); + } + + string pemCert = Path.Combine(directory, "playwright-test.pem"); + string pemKey = Path.Combine(directory, "playwright-test-key.pem"); + if (File.Exists(pemCert) && File.Exists(pemKey)) + { + X509KeyStorageFlags pemFlags = X509KeyStorageFlags.Exportable; + if (OperatingSystem.IsWindows()) + { + // Without EphemeralKeySet, Windows SslStream aborts the + // handshake (unexpected EOF / ERR_CONNECTION_CLOSED). + pemFlags |= X509KeyStorageFlags.EphemeralKeySet; + } + + X509Certificate2 pem = X509Certificate2.CreateFromPemFile(pemCert, pemKey); + // Password-protected export is reliable across .NET/Windows; + // empty-password PKCS12 often yields an unusable private key. + byte[] pfxBytes = pem.Export(X509ContentType.Pkcs12, pfxPassword); + return X509CertificateLoader.LoadPkcs12(pfxBytes, pfxPassword, pemFlags); + } + + X509Certificate2 publicOnly = X509CertificateLoader.LoadCertificateFromFile(fullPath); + if (!publicOnly.HasPrivateKey) + { + throw new InvalidOperationException( + "HTTPS certificate at '" + fullPath + "' has no private key. " + + "Provide key.pfx or playwright-test.pem + playwright-test-key.pem."); + } + + return publicOnly; + } + public Task StartAsync() => _webHost.StartAsync(); public async Task StopAsync() @@ -302,6 +429,7 @@ public void Reset() _csp.Clear(); _subscribers.Clear(); _requestWaits.Clear(); + _arrivedRequestCounts.Clear(); GzipRoutes.Clear(); _onceWebSocket = null; _onceWebSocketAsync = null; @@ -367,6 +495,7 @@ internal async Task AcceptAndDispatchWebSocketAsync(HttpContext context) await _webSocketAcceptGate.WaitAsync().ConfigureAwait(false); WebSocket webSocket; Stream raw; + OfficialServerWebSocket official; Action once; Func onceAsync; bool waiting; @@ -374,14 +503,40 @@ internal async Task AcceptAndDispatchWebSocketAsync(HttpContext context) { TaskCompletionSource requestWaiter = _webSocketRequestWait; _webSocketRequestWait = null; + // Keep the live request: the upgrade holds the connection open + // until the test finishes reading headers (WS handshake). requestWaiter?.TrySetResult(context.Request); waiting = _webSocketWait != null; - (webSocket, raw) = await UpgradeToWebSocketAsync(context).ConfigureAwait(false); - NotifyWebSocket(new OfficialServerWebSocket(webSocket, raw)); once = _onceWebSocket; onceAsync = _onceWebSocketAsync; _onceWebSocket = null; _onceWebSocketAsync = null; + (webSocket, raw) = await UpgradeToWebSocketAsync(context).ConfigureAwait(false); + + // Legacy OnceWebSocketConnection handlers need System.Net.WebSockets.WebSocket. + // Give them ManagedWebSocket exclusively — do not also run raw-frame receive + // on the same stream. + bool legacyHandler = once != null || onceAsync != null; + if (legacyHandler && webSocket == null && raw != null) + { + string subProtocol = FirstRequestedProtocol(context); + webSocket = WebSocket.CreateFromStream( + raw, + isServer: true, + string.IsNullOrEmpty(subProtocol) ? null : subProtocol, + Timeout.InfiniteTimeSpan); + official = new OfficialServerWebSocket(webSocket); + } + else if (raw != null) + { + official = new OfficialServerWebSocket(raw); + } + else + { + official = new OfficialServerWebSocket(webSocket); + } + + NotifyWebSocket(official); if (once != null) { once(webSocket); @@ -395,7 +550,7 @@ internal async Task AcceptAndDispatchWebSocketAsync(HttpContext context) if (onceAsync != null) { await onceAsync(webSocket).ConfigureAwait(false); - if (webSocket.State == WebSocketState.Open) + if (webSocket != null && webSocket.State == WebSocketState.Open) { await ReceiveLoopAsync(webSocket, sendCloseMessage: false, CancellationToken.None).ConfigureAwait(false); } @@ -405,29 +560,31 @@ internal async Task AcceptAndDispatchWebSocketAsync(HttpContext context) if (once != null) { - await WaitUntilDisconnectedAsync(webSocket).ConfigureAwait(false); + if (webSocket != null) + { + await WaitUntilDisconnectedAsync(webSocket).ConfigureAwait(false); + } + else + { + await official.WaitUntilClosedAsync().ConfigureAwait(false); + } + return; } if (waiting) { - await WaitUntilDisconnectedAsync(webSocket).ConfigureAwait(false); + // OfficialServerWebSocket owns the connection via raw frames / listeners. + await official.WaitUntilClosedAsync().ConfigureAwait(false); return; } if (!string.IsNullOrEmpty(_sendOnWebSocketConnection)) { - await webSocket.SendAsync( - new ArraySegment(Encoding.UTF8.GetBytes(_sendOnWebSocketConnection)), - WebSocketMessageType.Text, - true, - CancellationToken.None).ConfigureAwait(false); + official.Send(_sendOnWebSocketConnection); } - await ReceiveLoopAsync( - webSocket, - context.Request.Headers["User-Agent"].ToString().Contains("Firefox"), - CancellationToken.None).ConfigureAwait(false); + await official.WaitUntilClosedAsync().ConfigureAwait(false); } internal async Task<(WebSocket Socket, Stream Stream)> UpgradeToWebSocketAsync(HttpContext context) @@ -446,12 +603,13 @@ await ReceiveLoopAsync( } Stream stream = await upgrade.UpgradeAsync().ConfigureAwait(false); - WebSocket socket = WebSocket.CreateFromStream( - stream, - isServer: true, - string.IsNullOrEmpty(subProtocol) ? null : subProtocol, - TimeSpan.FromSeconds(30)); - return (socket, stream); + + // Return the raw upgraded stream without wrapping ManagedWebSocket. + // Sharing the stream with CreateFromStream lets ManagedWebSocket abort + // the connection during client-initiated application close codes + // (macOS WebKit then reports error + close 1006 instead of a clean + // echo). OfficialServerWebSocket owns the raw frames instead. + return (null, stream); } WebSocket accepted = string.IsNullOrEmpty(subProtocol) @@ -496,19 +654,93 @@ public void Subscribe(string path, Action action) public async Task WaitForRequest(string path, Func selector) { - var taskCompletion = new TaskCompletionSource(); + var taskCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _requestWaits[path] = context => { - taskCompletion.SetResult(selector(context.Request)); + T result = selector(context.Request); + // Kestrel pools and resets (and may dispose) the request once + // the connection finishes, so a live reference captured here + // can throw ObjectDisposedException or read empty/wrong values + // by the time the caller awaits this task. Snapshot now. + if (result is IHeaderDictionary headers) + { + result = (T)(object)SnapshotHeaders(headers); + } + else if (result is HttpRequest liveRequest) + { + result = (T)(object)new SnapshotHttpRequest(liveRequest); + } + + taskCompletion.TrySetResult(result); }; - var request = await taskCompletion.Task; + var request = await taskCompletion.Task.ConfigureAwait(false); _requestWaits.Remove(path); return request; } - public Task WaitForRequest(string path) => WaitForRequest(path, _ => true); + public Task WaitForRequest(string path) + { + // WebKit can deliver the document request after frame.GoToAsync starts + // but before this waiter is registered (frame-goto matching-responses). + // Consume a buffered arrival so the test does not hang forever. + if (TryConsumeArrivedRequest(path)) + { + return Task.CompletedTask; + } + + TaskCompletionSource taskCompletion = new(TaskCreationOptions.RunContinuationsAsynchronously); + _requestWaits[path] = _ => taskCompletion.TrySetResult(true); + + if (TryConsumeArrivedRequest(path)) + { + _requestWaits.Remove(path); + return Task.CompletedTask; + } + + return AwaitAndClearWaitAsync(path, taskCompletion.Task); + } + + private async Task AwaitAndClearWaitAsync(string path, Task waitTask) + { + try + { + await waitTask.ConfigureAwait(false); + } + finally + { + _requestWaits.Remove(path); + } + } + + private bool TryConsumeArrivedRequest(string path) + { + while (true) + { + if (!_arrivedRequestCounts.TryGetValue(path, out int count) || count <= 0) + { + return false; + } + + if (_arrivedRequestCounts.TryUpdate(path, count - 1, count)) + { + return true; + } + } + } + + private void RecordArrivedRequest(HttpContext context) + { + string path = context.Request.Path.Value ?? string.Empty; + string pathAndQuery = path + + (context.Request.QueryString.HasValue ? context.Request.QueryString.Value.ToString() : string.Empty); + _arrivedRequestCounts.AddOrUpdate(pathAndQuery, 1, static (_, n) => n + 1); + if (!string.Equals(pathAndQuery, path, StringComparison.Ordinal)) + { + _arrivedRequestCounts.AddOrUpdate(path, 1, static (_, n) => n + 1); + } + } /// /// Official server.waitForWebSocketConnectionRequest(). @@ -640,6 +872,7 @@ public sealed class UpgradeConnection private readonly TaskCompletionSource _done = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); private OfficialServerWebSocket _socket; + private bool _httpResponseWritten; internal UpgradeConnection(HttpContext context, SimpleServer server) { @@ -671,7 +904,12 @@ public async Task DoUpgradeAsync() { (WebSocket webSocket, Stream stream) = await _server.UpgradeToWebSocketAsync(_context) .ConfigureAwait(false); - _socket = new OfficialServerWebSocket(webSocket, stream); + + // Prefer raw-stream ownership so client close codes (e.g. 3002) are + // echoed without ManagedWebSocket aborting the handshake on macOS WebKit. + _socket = stream != null + ? new OfficialServerWebSocket(stream) + : new OfficialServerWebSocket(webSocket); _server.NotifyWebSocket(_socket); return; } @@ -682,6 +920,7 @@ public async Task DoUpgradeAsync() /// /// Writes an HTTP response status line and headers, then finishes the response. + /// Official socket.write of a raw HTTP rejection (e.g. 403). /// /// A raw HTTP/1.1 response, including the status line. /// A task that completes when the response has been written. @@ -695,11 +934,22 @@ public async Task WriteAsync(string raw) { _context.Response.StatusCode = status; } + + if (parts.Length >= 3 && !string.IsNullOrEmpty(parts[2])) + { + // Preserve reason phrase when Kestrel exposes it via the response feature. + IHttpResponseFeature responseFeature = _context.Features.Get(); + if (responseFeature != null) + { + responseFeature.ReasonPhrase = parts[2].Trim(); + } + } } _context.Response.Headers.ContentLength = 0; _context.Response.Headers["Connection"] = "close"; await _context.Response.CompleteAsync().ConfigureAwait(false); + _httpResponseWritten = true; } /// @@ -733,7 +983,14 @@ public void Destroy() try { _socket?.Destroy(); - _context.Abort(); + + // After a normal HTTP rejection (WriteAsync), do not Abort the + // connection — that races WebKit into status 0 / "Connection + // reset by peer" instead of the written 403 Forbidden. + if (!_httpResponseWritten && !_context.Response.HasStarted) + { + _context.Abort(); + } } catch (ObjectDisposedException) { @@ -745,5 +1002,157 @@ public void Destroy() _done.TrySetResult(true); } } + + private static HeaderDictionary SnapshotHeaders(IHeaderDictionary headers) + { + // Must stay case-insensitive: callers look up "user-agent" while the + // wire name is often "User-Agent". A default Dictionary comparer + // would make HeaderDictionary indexer miss and return empty. + Dictionary copy = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair pair in headers) + { + copy[pair.Key] = pair.Value; + } + + return new HeaderDictionary(copy); + } + + /// + /// Immutable view of an taken while the + /// Kestrel feature collection is still alive. Callers that retain a + /// request from or + /// must not touch the + /// live ASP.NET request after the response completes. + /// + private sealed class SnapshotHttpRequest : HttpRequest + { + private readonly HeaderDictionary _headers; + private readonly string _method; + private readonly PathString _path; + private readonly PathString _pathBase; + private readonly QueryString _queryString; + private readonly string _scheme; + private readonly string _protocol; + private readonly HostString _host; + private readonly bool _isHttps; + private readonly string _contentType; + private readonly long? _contentLength; + private readonly QueryCollection _query; + + public SnapshotHttpRequest(HttpRequest source) + { + _headers = SnapshotHeaders(source.Headers); + _method = source.Method; + _path = source.Path; + _pathBase = source.PathBase; + _queryString = source.QueryString; + _scheme = source.Scheme; + _protocol = source.Protocol; + _host = source.Host; + _isHttps = source.IsHttps; + _contentType = source.ContentType; + _contentLength = source.ContentLength; + Dictionary queryCopy = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair pair in source.Query) + { + queryCopy[pair.Key] = pair.Value; + } + + _query = new QueryCollection(queryCopy); + } + + public override HttpContext HttpContext => throw new NotSupportedException(); + + public override string Method + { + get => _method; + set => throw new NotSupportedException(); + } + + public override string Scheme + { + get => _scheme; + set => throw new NotSupportedException(); + } + + public override bool IsHttps + { + get => _isHttps; + set => throw new NotSupportedException(); + } + + public override HostString Host + { + get => _host; + set => throw new NotSupportedException(); + } + + public override PathString PathBase + { + get => _pathBase; + set => throw new NotSupportedException(); + } + + public override PathString Path + { + get => _path; + set => throw new NotSupportedException(); + } + + public override QueryString QueryString + { + get => _queryString; + set => throw new NotSupportedException(); + } + + public override IQueryCollection Query + { + get => _query; + set => throw new NotSupportedException(); + } + + public override string Protocol + { + get => _protocol; + set => throw new NotSupportedException(); + } + + public override IHeaderDictionary Headers => _headers; + + public override IRequestCookieCollection Cookies + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override long? ContentLength + { + get => _contentLength; + set => throw new NotSupportedException(); + } + + public override string ContentType + { + get => _contentType; + set => throw new NotSupportedException(); + } + + public override Stream Body + { + get => Stream.Null; + set => throw new NotSupportedException(); + } + + public override bool HasFormContentType => false; + + public override Task ReadFormAsync(CancellationToken cancellationToken = default) + => Task.FromResult(FormCollection.Empty); + + public override IFormCollection Form + { + get => FormCollection.Empty; + set => throw new NotSupportedException(); + } + } } } diff --git a/src/PlaywrightNative.Tests/ApiRequestTests.cs b/src/PlaywrightNative.Tests/ApiRequestTests.cs index f52cc65f..298c3cce 100644 --- a/src/PlaywrightNative.Tests/ApiRequestTests.cs +++ b/src/PlaywrightNative.Tests/ApiRequestTests.cs @@ -23,6 +23,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -345,7 +346,7 @@ public async Task GetShouldThrowWhenFailOnStatusCode() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await context.APIRequest.GetAsync(TestConstants.ServerUrl + "/api-fail", new() { FailOnStatusCode = true }).ConfigureAwait(false); }); @@ -429,7 +430,7 @@ public async Task GetShouldThrowWhenTimeoutExceeded() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await context.APIRequest.GetAsync(TestConstants.ServerUrl + "/api-slow", new() { Timeout = 300 }).ConfigureAwait(false); }); @@ -540,11 +541,11 @@ public async Task GetShouldThrowWhenMaxRedirectsExceeded() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await context.APIRequest.GetAsync(TestConstants.ServerUrl + "/api-hop1", new() { MaxRedirects = 1 }).ConfigureAwait(false); }); - Assert.That(ex.Message, Does.Contain("maxRedirects")); + Assert.That(ex.Message, Does.Contain("Max redirect count exceeded")); } [PlaywrightTest("global-fetch.spec.ts", "APIRequest GET uses context ignoreHTTPSErrors")] @@ -628,7 +629,7 @@ public async Task ResponseDisposeShouldBlockBodyReads() await response.DisposeAsync().ConfigureAwait(false); await response.DisposeAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( async () => await response.TextAsync().ConfigureAwait(false)); Assert.That(ex.Message, Does.Contain("disposed")); } @@ -658,15 +659,18 @@ public async Task RequestDisposeShouldBlockFurtherFetches() await request.DisposeAsync().ConfigureAwait(false); await request.DisposeAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await request.GetAsync(TestConstants.ServerUrl + "/api-dispose-fetch").ConfigureAwait(false); }); - Assert.That(ex.Message, Does.Contain("disposed")); + Assert.That(ex.Message, Does.Contain("Target page, context or browser has been closed")); - IAPIResponse response = await context.APIRequest.GetAsync( - TestConstants.ServerUrl + "/api-dispose-fetch").ConfigureAwait(false); - Assert.That(await response.TextAsync().ConfigureAwait(false), Is.EqualTo("ok")); + // Upstream keeps the same disposed context.request; further calls fail. + PlaywrightException ex2 = Assert.ThrowsAsync(async () => + { + await context.APIRequest.GetAsync(TestConstants.ServerUrl + "/api-dispose-fetch").ConfigureAwait(false); + }); + Assert.That(ex2.Message, Does.Contain("Target page, context or browser has been closed")); } [PlaywrightTest("global-fetch.spec.ts", "APIRequest POST sends a JSON body")] @@ -1160,7 +1164,7 @@ public async Task GetShouldNotRetryWhenMaxRetriesIsZero() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); - PlaywrightNativeException resetError = Assert.ThrowsAsync(async () => + PlaywrightException resetError = Assert.ThrowsAsync(async () => { await context.APIRequest.GetAsync( TestConstants.ServerUrl + "/api-reset-once").ConfigureAwait(false); @@ -1254,11 +1258,11 @@ public async Task StandaloneDisposeShouldBlockFurtherFetches() await using IAPIRequestContext request = await Playwright.APIRequest.NewContextAsync().ConfigureAwait(false); await request.DisposeAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await request.GetAsync(TestConstants.ServerUrl + "/api-standalone-disposed").ConfigureAwait(false); }); - Assert.That(ex.Message, Does.Contain("disposed")); + Assert.That(ex.Message, Does.Contain("Target page, context or browser has been closed")); } [PlaywrightTest("global-fetch.spec.ts", "Playwright.APIRequest ignoreHTTPSErrors accepts untrusted TLS")] @@ -1355,7 +1359,7 @@ public async Task StandaloneTimeoutShouldApplyToEveryRequest() }); await using IAPIRequestContext request = await Playwright.APIRequest.NewContextAsync(new() { Timeout = 300 }).ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await request.GetAsync(TestConstants.ServerUrl + "/api-standalone-slow").ConfigureAwait(false); }); @@ -1383,7 +1387,7 @@ public async Task StandaloneFailOnStatusCodeShouldThrow() }); await using IAPIRequestContext request = await Playwright.APIRequest.NewContextAsync(new() { FailOnStatusCode = true }).ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await request.GetAsync(TestConstants.ServerUrl + "/api-standalone-404").ConfigureAwait(false); }); @@ -1411,11 +1415,11 @@ public async Task StandaloneMaxRedirectsShouldThrowWhenExceeded() Server.SetRedirect("/api-standalone-hop1", TestConstants.ServerUrl + "/api-standalone-hop2"); await using IAPIRequestContext request = await Playwright.APIRequest.NewContextAsync(new() { MaxRedirects = 1 }).ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync(async () => + PlaywrightException ex = Assert.ThrowsAsync(async () => { await request.GetAsync(TestConstants.ServerUrl + "/api-standalone-hop1").ConfigureAwait(false); }); - Assert.That(ex.Message, Does.Contain("maxRedirects")); + Assert.That(ex.Message, Does.Contain("Max redirect count exceeded")); } [PlaywrightTest("global-fetch.spec.ts", "Playwright.APIRequest storageState sends cookies")] diff --git a/src/PlaywrightNative.Tests/Assets/client-certificates/client/trusted/cert-legacy.pfx b/src/PlaywrightNative.Tests/Assets/client-certificates/client/trusted/cert-legacy.pfx new file mode 100644 index 00000000..9f06aa35 Binary files /dev/null and b/src/PlaywrightNative.Tests/Assets/client-certificates/client/trusted/cert-legacy.pfx differ diff --git a/src/PlaywrightNative.Tests/Assets/client-certificates/client/trusted/cert.pfx b/src/PlaywrightNative.Tests/Assets/client-certificates/client/trusted/cert.pfx new file mode 100644 index 00000000..391dea7b Binary files /dev/null and b/src/PlaywrightNative.Tests/Assets/client-certificates/client/trusted/cert.pfx differ diff --git a/src/PlaywrightNative.Tests/BrowserDataTests.cs b/src/PlaywrightNative.Tests/BrowserDataTests.cs index 7e8d54ae..676fb268 100644 --- a/src/PlaywrightNative.Tests/BrowserDataTests.cs +++ b/src/PlaywrightNative.Tests/BrowserDataTests.cs @@ -5,6 +5,7 @@ // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 using System.IO; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative; using PlaywrightNative.NUnit; @@ -72,7 +73,14 @@ public void DownloadUrlsUsesProvidedHostsOverMirrors() string[] hosts = ["https://example.test/playwright"]; string[] urls = BrowserData.DownloadUrls(SupportedBrowser.Chromium, "mac-arm64", "1219", hosts); Assert.That(urls, Has.Length.EqualTo(1)); - Assert.That(urls[0], Is.EqualTo("https://example.test/playwright/builds/chromium/1219/chromium-mac-arm64.zip")); + // Chromium ships as Chrome for Testing: archive path uses ChromiumBrowserVersion + // under builds/cft/, ignoring the Playwright revision directory name. + Assert.That( + urls[0], + Is.EqualTo( + "https://example.test/playwright/builds/cft/" + + BrowserData.ChromiumBrowserVersion + + "/mac-arm64/chrome-mac-arm64.zip")); } [PlaywrightTest("browser.spec.ts", "Download urls falls back to cdn mirrors")] @@ -80,17 +88,21 @@ public void DownloadUrlsUsesProvidedHostsOverMirrors() public void DownloadUrlsFallsBackToCdnMirrors() { string[] urls = BrowserData.DownloadUrls(SupportedBrowser.Chromium, "mac-arm64", "1219", null); - Assert.That(urls, Has.Length.EqualTo(2)); + Assert.That(urls, Has.Length.EqualTo(BrowserData.ChromiumCdnMirrors.Length)); Assert.That(urls[0], Does.StartWith("https://cdn.playwright.dev/")); - Assert.That(urls[1], Does.StartWith("https://playwright.download.prss.microsoft.com/")); - Assert.That(urls[0], Does.EndWith("chromium-mac-arm64.zip")); + Assert.That( + urls[0], + Does.EndWith( + "/builds/cft/" + + BrowserData.ChromiumBrowserVersion + + "/mac-arm64/chrome-mac-arm64.zip")); } [PlaywrightTest("browser.spec.ts", "Download urls throws for unsupported platform key")] [Test] public void DownloadUrlsThrowsForUnsupportedPlatformKey() { - Assert.Throws(() => + Assert.Throws(() => BrowserData.DownloadUrls(SupportedBrowser.Chromium, "platform-that-does-not-exist", "1219", null)); } diff --git a/src/PlaywrightNative.Tests/BrowserFetcherTests.cs b/src/PlaywrightNative.Tests/BrowserFetcherTests.cs index fd56d5b1..618c881b 100644 --- a/src/PlaywrightNative.Tests/BrowserFetcherTests.cs +++ b/src/PlaywrightNative.Tests/BrowserFetcherTests.cs @@ -14,19 +14,36 @@ namespace PlaywrightNative.Tests { [TestFixture] + [NonParallelizable] public class BrowserFetcherTests { - [SetUp] - public void ClearEnvironmentBefore() => ClearEnvironment(); + private string _savedBrowsersPath; + private string _savedDownloadHost; + private string _savedDownloadTimeout; - [TearDown] - public void ClearEnvironment() + [SetUp] + public void SaveAndClearEnvironment() { + // These env vars are process-wide. Clearing them without restore makes + // BrowserType.ExecutablePath look at the default cache while launches + // still use the path resolved under PLAYWRIGHT_BROWSERS_PATH (CI), so + // ExecutablePath returns "" after a successful WebKit launch. + _savedBrowsersPath = Environment.GetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH"); + _savedDownloadHost = Environment.GetEnvironmentVariable("PLAYWRIGHT_DOWNLOAD_HOST"); + _savedDownloadTimeout = Environment.GetEnvironmentVariable("PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT"); Environment.SetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH", null); Environment.SetEnvironmentVariable("PLAYWRIGHT_DOWNLOAD_HOST", null); Environment.SetEnvironmentVariable("PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT", null); } + [TearDown] + public void RestoreEnvironment() + { + Environment.SetEnvironmentVariable("PLAYWRIGHT_BROWSERS_PATH", _savedBrowsersPath); + Environment.SetEnvironmentVariable("PLAYWRIGHT_DOWNLOAD_HOST", _savedDownloadHost); + Environment.SetEnvironmentVariable("PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT", _savedDownloadTimeout); + } + [PlaywrightTest("browsers-path.spec.ts", "Defaults to chromium and current platform")] [Test] public void DefaultsToChromiumAndCurrentPlatform() @@ -123,7 +140,7 @@ public void GetExecutablePathBuildsPathInCacheDir() { BrowserFetcher fetcher = new(new BrowserFetcherOptions { Path = "/tmp/cache", Platform = Platform.Linux }); string actual = fetcher.GetExecutablePath("9999"); - Assert.That(actual, Is.EqualTo(Path.Combine("/tmp/cache", "chromium-9999", "chrome-linux", "chrome"))); + Assert.That(actual, Is.EqualTo(Path.Combine("/tmp/cache", "chromium-9999", "chrome-linux64", "chrome"))); } [PlaywrightTest("browsers-path.spec.ts", "Get installed browsers returns empty when cache missing")] @@ -203,7 +220,7 @@ public void UninstallIsNoOpWhenBuildMissing() [Test] public void DownloadAsyncNoArgDelegatesToDefaultBuild() { - // Cache hit path: marker present, no network involved. + // Cache hit path: marker + executable present, no network involved. string tempCache = Path.Combine(Path.GetTempPath(), "pwsharp-fetcher-test-" + Guid.NewGuid()); Directory.CreateDirectory(tempCache); try @@ -219,6 +236,12 @@ public void DownloadAsyncNoArgDelegatesToDefaultBuild() Platform = Platform.Linux, }); + // DownloadAsync only treats marker trees as installed when the + // real executable exists (stale CI caches must re-extract). + string executable = fetcher.GetExecutablePath(BrowserData.ChromiumRevision); + Directory.CreateDirectory(Path.GetDirectoryName(executable)); + File.WriteAllText(executable, string.Empty); + InstalledBrowser installed = fetcher.DownloadAsync().GetAwaiter().GetResult(); Assert.That(installed.Browser, Is.EqualTo(SupportedBrowser.Chromium)); diff --git a/src/PlaywrightNative.Tests/BrowserTests.cs b/src/PlaywrightNative.Tests/BrowserTests.cs index 6869d8bc..85bc38f2 100644 --- a/src/PlaywrightNative.Tests/BrowserTests.cs +++ b/src/PlaywrightNative.Tests/BrowserTests.cs @@ -126,7 +126,10 @@ public async Task WaitForDisconnectedShouldTimeout() TimeoutException ex = Assert.ThrowsAsync( async () => await browser.WaitForDisconnectedAsync(timeout: 200).ConfigureAwait(false)); Assert.That(ex.Message, Does.Contain("browser.waitForEvent")); - Assert.That(ex.Message, Does.Contain("Timeout 200ms exceeded.")); + // Node page._waitForEvent: `Timeout Nms exceeded while waiting for event "${event}"` + Assert.That( + ex.Message, + Does.Contain("Timeout 200ms exceeded while waiting for event \"disconnected\"")); } [PlaywrightTest("browser.spec.ts", "BrowserType reports the launched engine")] diff --git a/src/PlaywrightNative.Tests/CDPSessionTests.cs b/src/PlaywrightNative.Tests/CDPSessionTests.cs index 03d1c7c0..3a63f5e6 100644 --- a/src/PlaywrightNative.Tests/CDPSessionTests.cs +++ b/src/PlaywrightNative.Tests/CDPSessionTests.cs @@ -16,6 +16,7 @@ */ using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -141,11 +142,11 @@ public async Task WebKitShouldRejectCdpSession() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException pageEx = Assert.ThrowsAsync( + PlaywrightException pageEx = Assert.ThrowsAsync( async () => await page.NewCDPSessionAsync().ConfigureAwait(false)); Assert.That(pageEx.Message, Does.Contain("Chromium")); - PlaywrightNativeException browserEx = Assert.ThrowsAsync( + PlaywrightException browserEx = Assert.ThrowsAsync( async () => await browser.NewBrowserCDPSessionAsync().ConfigureAwait(false)); Assert.That(browserEx.Message, Does.Contain("Chromium")); } @@ -168,7 +169,7 @@ public async Task FrameSessionShouldEvaluate() IElementHandle iframe = await page.QuerySelectorAsync("iframe").ConfigureAwait(false); IFrame frame = await iframe.ContentFrameAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( async () => await context.NewCDPSessionAsync(frame).ConfigureAwait(false)); Assert.That( ex.Message, diff --git a/src/PlaywrightNative.Tests/CheckStrictTests.cs b/src/PlaywrightNative.Tests/CheckStrictTests.cs index eee5ea80..2ea1d6ec 100644 --- a/src/PlaywrightNative.Tests/CheckStrictTests.cs +++ b/src/PlaywrightNative.Tests/CheckStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.CheckAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.CheckAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/Chromium/CRCheckboxTests.cs b/src/PlaywrightNative.Tests/Chromium/CRCheckboxTests.cs index 0ed78efd..241c544c 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRCheckboxTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRCheckboxTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -102,7 +103,7 @@ public async Task UncheckShouldThrowForRadioButton() await Page.GoToAsync("data:text/html,").ConfigureAwait(false); await using CRElementHandle handle = await Page.QuerySelectorAsync("#r").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.UncheckAsync()); Assert.That(ex.Message, Does.Contain("radio").IgnoreCase); } @@ -114,7 +115,7 @@ public async Task IsCheckedShouldThrowForNonCheckbox() await Page.GoToAsync("data:text/html,").ConfigureAwait(false); await using CRElementHandle handle = await Page.QuerySelectorAsync("#t").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.IsCheckedAsync()); Assert.That(ex.Message, Does.Contain("checkbox").Or.Contain("radio")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRDragTests.cs b/src/PlaywrightNative.Tests/Chromium/CRDragTests.cs index 31b6e6b9..1ae08384 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRDragTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRDragTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -94,7 +95,7 @@ await Page.GoToAsync(@"data:text/html, await using CRElementHandle dst = await Page.QuerySelectorAsync("#dst").ConfigureAwait(false); CRElementHandle src = await Page.QuerySelectorAsync("#src").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => src.DragToAsync(dst)); Assert.That(ex.Message, Does.Contain("layout").Or.Contain("visible")); diff --git a/src/PlaywrightNative.Tests/Chromium/CRElementHandleTests.cs b/src/PlaywrightNative.Tests/Chromium/CRElementHandleTests.cs index b0847bba..3e16e4ff 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRElementHandleTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRElementHandleTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.Input; @@ -115,7 +116,7 @@ public async Task ClickShouldThrowForInvisibleElement() await Page.GoToAsync("data:text/html,").ConfigureAwait(false); CRElementHandle handle = await Page.QuerySelectorAsync("#b").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.ClickAsync()); Assert.That(ex.Message, Does.Contain("no layout").Or.Contain("not visible")); @@ -132,7 +133,7 @@ public async Task DisposeShouldReleaseHandle() await handle.DisposeAsync().ConfigureAwait(false); Assert.That(handle.IsDisposed, Is.True); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.FocusAsync()); Assert.That(ex.Message, Does.Contain("disposed")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CREvaluationTests.cs b/src/PlaywrightNative.Tests/Chromium/CREvaluationTests.cs index 45fdc3a8..5933a01a 100644 --- a/src/PlaywrightNative.Tests/Chromium/CREvaluationTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CREvaluationTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -106,7 +107,7 @@ public async Task ShouldAwaitPromiseFromFunction() [Test, Timeout(TestConstants.DefaultTestTimeout)] public async Task ShouldThrowOnEvaluationError() { - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => Page.EvaluateAsync("throw new Error('test error')")); Assert.That(ex.Message, Does.Contain("test error")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRExposeFunctionTests.cs b/src/PlaywrightNative.Tests/Chromium/CRExposeFunctionTests.cs index d118c17d..7f17ed62 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRExposeFunctionTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRExposeFunctionTests.cs @@ -43,8 +43,11 @@ public async Task ShouldCallExposedFunctionWithArguments() { await Page.ExposeFunctionAsync("add", args => { - int a = args[0].GetInt32(); - int b = args[1].GetInt32(); + // The wire format tags each argument ({ n: 3 }, { s: "x" }, …) so + // ExposeFunctionBinder.Arg can tell types and cycles apart; this + // low-level handler reads the raw JsonElement[] itself. + int a = args[0].GetProperty("n").GetInt32(); + int b = args[1].GetProperty("n").GetInt32(); return Task.FromResult(a + b); }).ConfigureAwait(false); @@ -74,7 +77,7 @@ public async Task ShouldSupportAsyncHandler() await Page.ExposeFunctionAsync("slowDouble", async args => { await Task.Delay(50).ConfigureAwait(false); - return (object)(args[0].GetInt32() * 2); + return (object)(args[0].GetProperty("n").GetInt32() * 2); }).ConfigureAwait(false); int result = await Page.EvaluateAsync("window.slowDouble(5)").ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/Chromium/CRFileInputTests.cs b/src/PlaywrightNative.Tests/Chromium/CRFileInputTests.cs index 53c63601..ae8cba69 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRFileInputTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRFileInputTests.cs @@ -16,6 +16,7 @@ */ using System.Text; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -102,7 +103,7 @@ public async Task ShouldThrowOnNonFileInput() await Page.GoToAsync("data:text/html,").ConfigureAwait(false); await using CRElementHandle handle = await Page.QuerySelectorAsync("#t").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.SetInputFilesAsync(new FilePayload { Name = "x.txt", MimeType = "text/plain", Buffer = new byte[] { 1 } })); Assert.That(ex.Message, Does.Contain("file")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRFillTests.cs b/src/PlaywrightNative.Tests/Chromium/CRFillTests.cs index 35d0dadd..a28d0845 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRFillTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRFillTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -142,7 +143,7 @@ public async Task ShouldThrowWhenElementIsNotFillable() await Page.GoToAsync("data:text/html,
not fillable
").ConfigureAwait(false); await using CRElementHandle handle = await Page.QuerySelectorAsync("#d").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.FillAsync("anything")); Assert.That(ex.Message, Does.Contain("input").Or.Contain("textarea")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRKeyboardTests.cs b/src/PlaywrightNative.Tests/Chromium/CRKeyboardTests.cs index 082f359a..3338809a 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRKeyboardTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRKeyboardTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -182,7 +183,7 @@ public async Task ShouldThrowOnUnknownKey() { await Page.GoToAsync(TestConstants.ServerUrl + "/input/textarea.html").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => Page.Keyboard.PressAsync("NotARealKey")); Assert.That(ex.Message, Does.Contain("NotARealKey")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRRouteTests.cs b/src/PlaywrightNative.Tests/Chromium/CRRouteTests.cs index 0111d45d..ba5d29a4 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRRouteTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRRouteTests.cs @@ -19,6 +19,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -57,11 +58,16 @@ public async Task ShouldReceiveFetchEvents() { // Test raw CDP Fetch: enable it, listen for requestPaused, manually continue. var fetchEvents = new List(); + object fetchEventsGate = new object(); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Page.Session.MessageReceived += (method, param) => { - fetchEvents.Add(method); + lock (fetchEventsGate) + { + fetchEvents.Add(method); + } + if (method == "Fetch.requestPaused" && param.HasValue) { string interceptionId = param.Value.GetProperty("requestId").GetString(); @@ -96,7 +102,13 @@ public async Task ShouldReceiveFetchEvents() await navTask.ConfigureAwait(false); } - string fetchEventsStr = string.Join(", ", fetchEvents.Where(e => e.StartsWith("Fetch.") || e.StartsWith("Network.request"))); + string[] snapshot; + lock (fetchEventsGate) + { + snapshot = fetchEvents.ToArray(); + } + + string fetchEventsStr = string.Join(", ", snapshot.Where(e => e.StartsWith("Fetch.") || e.StartsWith("Network.request"))); TestContext.Out.WriteLine($"Relevant events: {fetchEventsStr}"); Assert.That(received, Is.True, $"Fetch.requestPaused not received. Events: [{fetchEventsStr}]"); } @@ -147,7 +159,7 @@ await Page.RouteAsync("**/abort.html", async route => { navigationFailed = true; } - catch (PlaywrightNativeException) + catch (PlaywrightException) { navigationFailed = true; } diff --git a/src/PlaywrightNative.Tests/Chromium/CRScriptTagTests.cs b/src/PlaywrightNative.Tests/Chromium/CRScriptTagTests.cs index 2105678a..5c9fc9e6 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRScriptTagTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRScriptTagTests.cs @@ -16,6 +16,7 @@ */ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -60,7 +61,9 @@ public async Task ShouldAddScriptWithUrl() [Test, Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowWhenNeitherUrlNorContent() { - System.ArgumentException ex = Assert.ThrowsAsync( + // Matches PageAddScriptTagTests.ShouldThrowAnErrorIfNoOptionsAreProvided: + // official validation errors surface as PlaywrightException here. + PlaywrightException ex = Assert.ThrowsAsync( () => Page.AddScriptTagAsync()); Assert.That(ex.Message, Does.Contain("url").Or.Contain("content")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRSelectTests.cs b/src/PlaywrightNative.Tests/Chromium/CRSelectTests.cs index 66d32fe7..c539a2ab 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRSelectTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRSelectTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.Input; @@ -146,7 +147,7 @@ public async Task ShouldThrowWhenElementIsNotSelect() await Page.GoToAsync("data:text/html,
not a select
").ConfigureAwait(false); await using CRElementHandle handle = await Page.QuerySelectorAsync("#d").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.SelectOptionAsync("anything")); Assert.That(ex.Message, Does.Contain("select")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRStyleTagTests.cs b/src/PlaywrightNative.Tests/Chromium/CRStyleTagTests.cs index 76e72f3e..ba0a3345 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRStyleTagTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRStyleTagTests.cs @@ -16,6 +16,7 @@ */ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -63,7 +64,9 @@ public async Task ShouldAddStyleWithUrl() [Test, Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowWhenNeitherUrlNorContent() { - System.ArgumentException ex = Assert.ThrowsAsync( + // Matches PageAddStyleTagTests.ShouldThrowAnErrorIfNoOptionsAreProvided: + // official validation errors surface as PlaywrightException here. + PlaywrightException ex = Assert.ThrowsAsync( () => Page.AddStyleTagAsync()); Assert.That(ex.Message, Does.Contain("url").Or.Contain("content")); } diff --git a/src/PlaywrightNative.Tests/Chromium/CRTapTests.cs b/src/PlaywrightNative.Tests/Chromium/CRTapTests.cs index d49be2ab..598f9afc 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRTapTests.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRTapTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -71,7 +72,7 @@ public async Task ShouldThrowForInvisibleElement() await Page.GoToAsync("data:text/html,").ConfigureAwait(false); CRElementHandle handle = await Page.QuerySelectorAsync("#t").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => handle.TapAsync()); Assert.That(ex.Message, Does.Contain("no layout").Or.Contain("not visible")); diff --git a/src/PlaywrightNative.Tests/Chromium/CRTestBase.cs b/src/PlaywrightNative.Tests/Chromium/CRTestBase.cs index b135cd30..dafb47cd 100644 --- a/src/PlaywrightNative.Tests/Chromium/CRTestBase.cs +++ b/src/PlaywrightNative.Tests/Chromium/CRTestBase.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.NUnit; @@ -101,7 +102,7 @@ public async Task CRTearDown() { // Browser session may already be closed. } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Connection may already be closed. } diff --git a/src/PlaywrightNative.Tests/ClickStrictTests.cs b/src/PlaywrightNative.Tests/ClickStrictTests.cs index 4b1d65d6..2ebc8015 100644 --- a/src/PlaywrightNative.Tests/ClickStrictTests.cs +++ b/src/PlaywrightNative.Tests/ClickStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoButtonsMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.ClickAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -52,10 +53,10 @@ public async Task StrictTrueShouldAcceptAUniqueSelector() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - await page.SetContentAsync("
").ConfigureAwait(false); + await page.SetContentAsync("
").ConfigureAwait(false); await page.ClickAsync("#only", new() { Strict = true }).ConfigureAwait(false); - string id = await page.EvaluateAsync("document.activeElement && document.activeElement.id").ConfigureAwait(false); + string id = await page.EvaluateAsync("window.lastClickedId").ConfigureAwait(false); Assert.That(id, Is.EqualTo("only")); } @@ -70,10 +71,10 @@ public async Task StrictFalseShouldOverrideContextStrictSelectors() StrictSelectors = true, }).ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - await page.SetContentAsync("
").ConfigureAwait(false); + await page.SetContentAsync("
").ConfigureAwait(false); await page.ClickAsync("button", new() { Strict = false }).ConfigureAwait(false); - string id = await page.EvaluateAsync("document.activeElement && document.activeElement.id").ConfigureAwait(false); + string id = await page.EvaluateAsync("window.lastClickedId").ConfigureAwait(false); Assert.That(id, Is.EqualTo("first")); } @@ -96,7 +97,7 @@ public async Task FrameClickShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.ClickAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/ClockInstallOptionsTests.cs b/src/PlaywrightNative.Tests/ClockInstallOptionsTests.cs index 0a2dac0d..65a62c6e 100644 --- a/src/PlaywrightNative.Tests/ClockInstallOptionsTests.cs +++ b/src/PlaywrightNative.Tests/ClockInstallOptionsTests.cs @@ -41,6 +41,7 @@ public async Task TimeDateShouldFreezeDateNow() const long frozen = 1_706_871_600_000; DateTime timeDate = DateTimeOffset.FromUnixTimeMilliseconds(frozen).UtcDateTime; await page.Clock.InstallAsync(new ClockInstallOptions { TimeDate = timeDate }).ConfigureAwait(false); + await page.Clock.PauseAtAsync(frozen).ConfigureAwait(false); long now = await page.EvaluateAsync("Date.now()").ConfigureAwait(false); Assert.That(now, Is.EqualTo(frozen)); @@ -60,6 +61,7 @@ public async Task TimeShouldFreezeDateNow() DateTime parsed = DateTime.Parse(time, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); long expected = new DateTimeOffset(parsed.ToUniversalTime()).ToUnixTimeMilliseconds(); await page.Clock.InstallAsync(new ClockInstallOptions { Time = time }).ConfigureAwait(false); + await page.Clock.PauseAtAsync(expected).ConfigureAwait(false); long now = await page.EvaluateAsync("Date.now()").ConfigureAwait(false); Assert.That(now, Is.EqualTo(expected)); @@ -79,6 +81,7 @@ public async Task TimeStringShouldFreezeDateNow() DateTime parsed = DateTime.Parse(time, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); long expected = new DateTimeOffset(parsed.ToUniversalTime()).ToUnixTimeMilliseconds(); await page.Clock.InstallAsync(new ClockInstallOptions { TimeString = time }).ConfigureAwait(false); + await page.Clock.PauseAtAsync(expected).ConfigureAwait(false); long now = await page.EvaluateAsync("Date.now()").ConfigureAwait(false); Assert.That(now, Is.EqualTo(expected)); @@ -101,6 +104,7 @@ await page.Clock.InstallAsync(new ClockInstallOptions TimeDate = timeDate, Time = "1999-01-01T00:00:00.000Z", }).ConfigureAwait(false); + await page.Clock.PauseAtAsync(frozen).ConfigureAwait(false); long now = await page.EvaluateAsync("Date.now()").ConfigureAwait(false); Assert.That(now, Is.EqualTo(frozen)); diff --git a/src/PlaywrightNative.Tests/ClockTests.cs b/src/PlaywrightNative.Tests/ClockTests.cs index 3b9b7f52..685bd78d 100644 --- a/src/PlaywrightNative.Tests/ClockTests.cs +++ b/src/PlaywrightNative.Tests/ClockTests.cs @@ -90,7 +90,7 @@ public async Task InstallShouldFreezeDateNow() const long frozen = 1_706_871_600_000; await page.Clock.InstallAsync(frozen).ConfigureAwait(false); - + await page.Clock.PauseAtAsync(frozen).ConfigureAwait(false); long now = await page.EvaluateAsync("Date.now()").ConfigureAwait(false); Assert.That(now, Is.EqualTo(frozen)); } @@ -107,6 +107,7 @@ public async Task FastForwardShouldFireSetTimeout() const long frozen = 1_706_871_600_000; await page.Clock.InstallAsync(frozen).ConfigureAwait(false); + await page.Clock.PauseAtAsync(frozen).ConfigureAwait(false); await page.EvaluateAsync("window.__fired = 0; setTimeout(() => { window.__fired = 1; }, 1000);").ConfigureAwait(false); Assert.That(await page.EvaluateAsync("window.__fired").ConfigureAwait(false), Is.EqualTo(0)); @@ -127,6 +128,7 @@ public async Task RunForShouldAcceptMinuteSecondString() const long frozen = 1_706_871_600_000; await page.Clock.InstallAsync(frozen).ConfigureAwait(false); + await page.Clock.PauseAtAsync(frozen).ConfigureAwait(false); await page.Clock.RunForAsync("00:02").ConfigureAwait(false); Assert.That(await page.EvaluateAsync("Date.now()").ConfigureAwait(false), Is.EqualTo(frozen + 2000)); @@ -163,6 +165,7 @@ public async Task ResumeShouldLetDateNowProgress() const long frozen = 1_706_871_600_000; await page.Clock.InstallAsync(frozen).ConfigureAwait(false); + await page.Clock.PauseAtAsync(frozen).ConfigureAwait(false); await page.Clock.ResumeAsync().ConfigureAwait(false); await page.WaitForTimeoutAsync(50).ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/ConnectOverCdpTests.cs b/src/PlaywrightNative.Tests/ConnectOverCdpTests.cs index 1b18c716..31f638ef 100644 --- a/src/PlaywrightNative.Tests/ConnectOverCdpTests.cs +++ b/src/PlaywrightNative.Tests/ConnectOverCdpTests.cs @@ -40,12 +40,34 @@ private static async Task WaitForDevToolsEndpointAsync(string userData) { if (File.Exists(portFile)) { - string[] lines = await File.ReadAllLinesAsync(portFile).ConfigureAwait(false); - if (lines.Length > 0 - && int.TryParse(lines[0], out int port) - && port > 0) + // Chromium keeps DevToolsActivePort open with a write lock on + // Windows; share + retry across rewrite races. + try + { + string[] lines; + using (FileStream stream = new FileStream( + portFile, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite | FileShare.Delete)) + using (StreamReader reader = new StreamReader(stream)) + { + string text = await reader.ReadToEndAsync().ConfigureAwait(false); + lines = text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); + } + + if (lines.Length > 0 + && int.TryParse(lines[0], out int port) + && port > 0) + { + return "http://127.0.0.1:" + port; + } + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) { - return "http://127.0.0.1:" + port; } } diff --git a/src/PlaywrightNative.Tests/ConsoleOpenerFrameTests.cs b/src/PlaywrightNative.Tests/ConsoleOpenerFrameTests.cs index 70cde0b0..6079a189 100644 --- a/src/PlaywrightNative.Tests/ConsoleOpenerFrameTests.cs +++ b/src/PlaywrightNative.Tests/ConsoleOpenerFrameTests.cs @@ -18,6 +18,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -373,7 +374,7 @@ await page.EvaluateAsync(@" childSum = await child.EvaluateAsync("1 + 1").ConfigureAwait(false); childIsTop = await child.EvaluateAsync("window === window.top").ConfigureAwait(false); } - catch (PlaywrightNativeException) + catch (PlaywrightException) { await Task.Delay(100).ConfigureAwait(false); } diff --git a/src/PlaywrightNative.Tests/ContextCookieTests.cs b/src/PlaywrightNative.Tests/ContextCookieTests.cs index 7329c523..cd65735d 100644 --- a/src/PlaywrightNative.Tests/ContextCookieTests.cs +++ b/src/PlaywrightNative.Tests/ContextCookieTests.cs @@ -272,10 +272,13 @@ public async Task ClearCookiesShouldRemoveMatchingPathRegex() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + // Cookie API is url XOR domain/path (upstream network.py). Path-only + // cookies must use an explicit domain/path pair. + Uri empty = new Uri(TestConstants.EmptyPage); await context.AddCookiesAsync(new[] { - new Cookie { Name = "wave427-keep", Value = "yes", Url = TestConstants.EmptyPage, Path = "/", SameSite = SameSiteAttribute.Lax }, - new Cookie { Name = "wave427-drop", Value = "no", Url = TestConstants.EmptyPage, Path = "/grid.html", SameSite = SameSiteAttribute.Lax }, + new Cookie { Name = "wave427-keep", Value = "yes", Domain = empty.Host, Path = "/", SameSite = SameSiteAttribute.Lax }, + new Cookie { Name = "wave427-drop", Value = "no", Domain = empty.Host, Path = "/grid.html", SameSite = SameSiteAttribute.Lax }, }).ConfigureAwait(false); await context.ClearCookiesAsync(null, null, new Regex("grid\\.html$")).ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/ContextNetworkEventTests.cs b/src/PlaywrightNative.Tests/ContextNetworkEventTests.cs index e5050972..1a514b3c 100644 --- a/src/PlaywrightNative.Tests/ContextNetworkEventTests.cs +++ b/src/PlaywrightNative.Tests/ContextNetworkEventTests.cs @@ -62,11 +62,11 @@ public async Task ShouldWaitForMatchingRequest() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - Task waitTask = context.WaitForRequestAsync("data:text/html*"); - await page.GoToAsync("data:text/html,wave191").ConfigureAwait(false); + Task waitTask = context.WaitForRequestAsync(TestConstants.EmptyPage); + await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); IRequest request = await waitTask.ConfigureAwait(false); Assert.That(request, Is.Not.Null); - Assert.That(request.Url, Does.StartWith("data:text/html")); + Assert.That(request.Url, Is.EqualTo(TestConstants.EmptyPage)); } [PlaywrightTest("browsercontext-network-event.spec.ts", "RunAndWaitForRequestAsync returns the request")] @@ -79,11 +79,11 @@ public async Task RunAndWaitForRequestAsyncShouldReturnTheRequest() IPage page = await context.NewPageAsync().ConfigureAwait(false); IRequest request = await context.RunAndWaitForRequestAsync( - () => page.GoToAsync("data:text/html,wave310")) + () => page.GoToAsync(TestConstants.EmptyPage)) .ConfigureAwait(false); Assert.That(request, Is.Not.Null); - Assert.That(request.Url, Does.StartWith("data:text/html")); + Assert.That(request.Url, Is.EqualTo(TestConstants.EmptyPage)); } [PlaywrightTest("browsercontext-network-event.spec.ts", "WaitForRequestAsync times out")] @@ -134,11 +134,11 @@ public async Task ShouldWaitForMatchingResponse() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - Task waitTask = context.WaitForResponseAsync(new Regex("wave192")); - await page.GoToAsync("data:text/html,wave192").ConfigureAwait(false); + Task waitTask = context.WaitForResponseAsync(TestConstants.EmptyPage); + await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); IResponse response = await waitTask.ConfigureAwait(false); Assert.That(response, Is.Not.Null); - Assert.That(response.Url, Does.Contain("wave192")); + Assert.That(response.Url, Is.EqualTo(TestConstants.EmptyPage)); Assert.That(response.Ok, Is.True); } @@ -152,11 +152,11 @@ public async Task RunAndWaitForResponseAsyncShouldReturnTheResponse() IPage page = await context.NewPageAsync().ConfigureAwait(false); IResponse response = await context.RunAndWaitForResponseAsync( - () => page.GoToAsync("data:text/html,wave311")) + () => page.GoToAsync(TestConstants.EmptyPage)) .ConfigureAwait(false); Assert.That(response, Is.Not.Null); - Assert.That(response.Url, Does.Contain("wave311")); + Assert.That(response.Url, Is.EqualTo(TestConstants.EmptyPage)); Assert.That(response.Ok, Is.True); } @@ -207,11 +207,11 @@ public async Task ShouldWaitForMatchingFinishedRequest() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - Task waitTask = context.WaitForRequestFinishedAsync("data:text/html*"); - await page.GoToAsync("data:text/html,wave205").ConfigureAwait(false); + Task waitTask = context.WaitForRequestFinishedAsync(TestConstants.EmptyPage); + await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); IRequest request = await waitTask.ConfigureAwait(false); Assert.That(request, Is.Not.Null); - Assert.That(request.Url, Does.StartWith("data:text/html")); + Assert.That(request.Url, Is.EqualTo(TestConstants.EmptyPage)); Assert.That(request.Method, Is.EqualTo("GET")); } @@ -225,11 +225,11 @@ public async Task RunAndWaitForRequestFinishedAsyncShouldReturnTheRequest() IPage page = await context.NewPageAsync().ConfigureAwait(false); IRequest request = await context.RunAndWaitForRequestFinishedAsync( - () => page.GoToAsync("data:text/html,wave312")) + () => page.GoToAsync(TestConstants.EmptyPage)) .ConfigureAwait(false); Assert.That(request, Is.Not.Null); - Assert.That(request.Url, Does.StartWith("data:text/html")); + Assert.That(request.Url, Is.EqualTo(TestConstants.EmptyPage)); Assert.That(request.Method, Is.EqualTo("GET")); } @@ -242,10 +242,10 @@ public async Task ShouldWaitForFinishedRequestRegex() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - Task waitTask = context.WaitForRequestFinishedAsync(new Regex("wave205")); - await page.GoToAsync("data:text/html,wave205-re").ConfigureAwait(false); + Task waitTask = context.WaitForRequestFinishedAsync(new Regex(@"empty\.html")); + await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); IRequest request = await waitTask.ConfigureAwait(false); - Assert.That(request.Url, Does.Contain("wave205")); + Assert.That(request.Url, Does.Contain("empty.html")); } [PlaywrightTest("browsercontext-network-event.spec.ts", "WaitForRequestFinishedAsync matches a predicate")] @@ -258,10 +258,10 @@ public async Task ShouldWaitForFinishedRequestPredicate() IPage page = await context.NewPageAsync().ConfigureAwait(false); Task waitTask = context.WaitForRequestFinishedAsync( - r => r.Url.Contains("wave205-pred", StringComparison.Ordinal)); - await page.GoToAsync("data:text/html,wave205-pred").ConfigureAwait(false); + r => r.Url.Contains("empty.html", StringComparison.Ordinal)); + await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); IRequest request = await waitTask.ConfigureAwait(false); - Assert.That(request.Url, Does.Contain("wave205-pred")); + Assert.That(request.Url, Does.Contain("empty.html")); } [PlaywrightTest("browsercontext-network-event.spec.ts", "WaitForRequestFinishedAsync times out")] diff --git a/src/PlaywrightNative.Tests/DblClickStrictTests.cs b/src/PlaywrightNative.Tests/DblClickStrictTests.cs index 70e38c2d..74d6a187 100644 --- a/src/PlaywrightNative.Tests/DblClickStrictTests.cs +++ b/src/PlaywrightNative.Tests/DblClickStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoButtonsMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.DblClickAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameDblClickShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.DblClickAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/DispatchEventStrictTests.cs b/src/PlaywrightNative.Tests/DispatchEventStrictTests.cs index e37a590e..d8e01de0 100644 --- a/src/PlaywrightNative.Tests/DispatchEventStrictTests.cs +++ b/src/PlaywrightNative.Tests/DispatchEventStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoButtonsMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.DispatchEventAsync("button", "click", options: new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.DispatchEventAsync("button", "click", options: new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/DragAndDropStrictTests.cs b/src/PlaywrightNative.Tests/DragAndDropStrictTests.cs index ab918210..d9f7077c 100644 --- a/src/PlaywrightNative.Tests/DragAndDropStrictTests.cs +++ b/src/PlaywrightNative.Tests/DragAndDropStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoSourcesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
a
b
dst
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.DragAndDropAsync(".src", "#dst", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -106,7 +107,7 @@ public async Task FrameShouldHonorStrict() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
a
b
dst
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.MainFrame.DragAndDropAsync(".src", "#dst", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/ElementFrameTests.cs b/src/PlaywrightNative.Tests/ElementFrameTests.cs index 5e61c4dc..92502b37 100644 --- a/src/PlaywrightNative.Tests/ElementFrameTests.cs +++ b/src/PlaywrightNative.Tests/ElementFrameTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -114,7 +115,7 @@ public async Task ShouldThrowForMainFrame() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.GoToAsync("about:blank").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => page.MainFrame.FrameElementAsync()); Assert.That(ex.Message, Does.Contain("detached").IgnoreCase); } @@ -157,7 +158,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/ElementHandleClickParityTests.cs b/src/PlaywrightNative.Tests/ElementHandleClickParityTests.cs index e35492cb..674c1365 100644 --- a/src/PlaywrightNative.Tests/ElementHandleClickParityTests.cs +++ b/src/PlaywrightNative.Tests/ElementHandleClickParityTests.cs @@ -17,6 +17,7 @@ using System; using System.Globalization; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -147,7 +148,7 @@ public async Task ShouldThrowForDetachedNodes() await page.GoToAsync(Prefix + "/input/button.html").ConfigureAwait(false); IElementHandle button = await page.QuerySelectorAsync("button").ConfigureAwait(false); await button.EvaluateAsync("button => button.remove()").ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync(() => button.ClickAsync()); + PlaywrightException error = Assert.CatchAsync(() => button.ClickAsync()); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Element is not attached to the DOM")); } @@ -163,7 +164,7 @@ public async Task ShouldThrowForHiddenNodesWithForce() await page.GoToAsync(Prefix + "/input/button.html").ConfigureAwait(false); IElementHandle button = await page.QuerySelectorAsync("button").ConfigureAwait(false); await button.EvaluateAsync("button => button.style.display = 'none'").ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync(() => button.ClickAsync(new() { Force = true })); + PlaywrightException error = Assert.CatchAsync(() => button.ClickAsync(new() { Force = true })); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Element is not visible")); } @@ -179,7 +180,7 @@ public async Task ShouldThrowForRecursivelyHiddenNodesWithForce() await page.GoToAsync(Prefix + "/input/button.html").ConfigureAwait(false); IElementHandle button = await page.QuerySelectorAsync("button").ConfigureAwait(false); await button.EvaluateAsync("button => button.parentElement.style.display = 'none'").ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync(() => button.ClickAsync(new() { Force = true })); + PlaywrightException error = Assert.CatchAsync(() => button.ClickAsync(new() { Force = true })); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Element is not visible")); } @@ -194,7 +195,7 @@ public async Task ShouldThrowForBrElementsWithForce() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("hello
goodbye").ConfigureAwait(false); IElementHandle br = await page.QuerySelectorAsync("br").ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync(() => br.ClickAsync(new() { Force = true })); + PlaywrightException error = Assert.CatchAsync(() => br.ClickAsync(new() { Force = true })); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Element is outside of the viewport")); } diff --git a/src/PlaywrightNative.Tests/ElementHandleEvalOnSelectorParityTests.cs b/src/PlaywrightNative.Tests/ElementHandleEvalOnSelectorParityTests.cs index c9c0cd21..9b1d6b4d 100644 --- a/src/PlaywrightNative.Tests/ElementHandleEvalOnSelectorParityTests.cs +++ b/src/PlaywrightNative.Tests/ElementHandleEvalOnSelectorParityTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -69,7 +70,7 @@ public async Task ShouldThrowInCaseOfMissingSelector() string htmlContent = "
not-a-child-div
"; await page.SetContentAsync(htmlContent).ConfigureAwait(false); IElementHandle elementHandle = await page.QuerySelectorAsync("#myId").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => elementHandle.EvalOnSelectorAsync(".a", "node => node.innerText")); Assert.That(ex, Is.Not.Null); Assert.That(ex.Message, Does.Contain("Failed to find element matching selector \".a\"")); diff --git a/src/PlaywrightNative.Tests/ElementHandleInteractionTests.cs b/src/PlaywrightNative.Tests/ElementHandleInteractionTests.cs index 85ccd80c..9b870d2e 100644 --- a/src/PlaywrightNative.Tests/ElementHandleInteractionTests.cs +++ b/src/PlaywrightNative.Tests/ElementHandleInteractionTests.cs @@ -18,6 +18,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -826,7 +827,7 @@ await page.EvaluateAsync( { await hidden.CheckAsync(force: true).ConfigureAwait(false); } - catch (PlaywrightNativeException) + catch (PlaywrightException) { } @@ -1011,7 +1012,7 @@ await page.EvaluateAsync( { await hidden.UncheckAsync(force: true).ConfigureAwait(false); } - catch (PlaywrightNativeException) + catch (PlaywrightException) { } diff --git a/src/PlaywrightNative.Tests/ElementHandleScrollIntoViewParityTests.cs b/src/PlaywrightNative.Tests/ElementHandleScrollIntoViewParityTests.cs index e8a27daf..809598d4 100644 --- a/src/PlaywrightNative.Tests/ElementHandleScrollIntoViewParityTests.cs +++ b/src/PlaywrightNative.Tests/ElementHandleScrollIntoViewParityTests.cs @@ -17,6 +17,7 @@ using System; using System.Globalization; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -128,7 +129,7 @@ public async Task ShouldThrowForDetachedElement() await page.SetContentAsync("
Hello
").ConfigureAwait(false); IElementHandle div = await page.QuerySelectorAsync("div").ConfigureAwait(false); await div.EvaluateAsync("div => div.remove()").ConfigureAwait(false); - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( () => div.ScrollIntoViewIfNeededAsync()); Assert.That(error.Message, Does.Contain("Element is not attached to the DOM")); } diff --git a/src/PlaywrightNative.Tests/ElementHandleWaitForElementStateParityTests.cs b/src/PlaywrightNative.Tests/ElementHandleWaitForElementStateParityTests.cs index 66e980cf..66e43383 100644 --- a/src/PlaywrightNative.Tests/ElementHandleWaitForElementStateParityTests.cs +++ b/src/PlaywrightNative.Tests/ElementHandleWaitForElementStateParityTests.cs @@ -17,6 +17,7 @@ using System; using System.Globalization; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -148,7 +149,7 @@ public async Task ShouldThrowWaitingForVisibleWhenDetached() IElementHandle div = await page.QuerySelectorAsync("div").ConfigureAwait(false); Task waitTask = div.WaitForElementStateAsync(ElementState.Visible); await div.EvaluateAsync("div => div.remove()").ConfigureAwait(false); - PlaywrightNativeException error = Assert.ThrowsAsync(() => waitTask); + PlaywrightException error = Assert.ThrowsAsync(() => waitTask); Assert.That(error.Message, Does.Contain("Element is not attached to the DOM")); } @@ -225,7 +226,7 @@ public async Task ShouldThrowWaitingForEnabledWhenDetached() IElementHandle button = await page.QuerySelectorAsync("button").ConfigureAwait(false); Task waitTask = button.WaitForElementStateAsync(ElementState.Enabled); await button.EvaluateAsync("button => button.remove()").ConfigureAwait(false); - PlaywrightNativeException error = Assert.ThrowsAsync(() => waitTask); + PlaywrightException error = Assert.ThrowsAsync(() => waitTask); Assert.That(error.Message, Does.Contain("Element is not attached to the DOM")); } diff --git a/src/PlaywrightNative.Tests/ElementWaitForSelectorStrictTests.cs b/src/PlaywrightNative.Tests/ElementWaitForSelectorStrictTests.cs index b6110742..25540e4f 100644 --- a/src/PlaywrightNative.Tests/ElementWaitForSelectorStrictTests.cs +++ b/src/PlaywrightNative.Tests/ElementWaitForSelectorStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -37,7 +38,7 @@ public async Task StrictTrueShouldThrowWhenTwoDescendantsMatch() await page.SetContentAsync("
").ConfigureAwait(false); IElementHandle root = await page.QuerySelectorAsync("#root").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => root.WaitForSelectorAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -100,7 +101,7 @@ public async Task FrameElementShouldHonorStrict() await frame.SetContentAsync("
").ConfigureAwait(false); IElementHandle root = await frame.QuerySelectorAsync("#root").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => root.WaitForSelectorAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/EvalOnSelectorParityTests.cs b/src/PlaywrightNative.Tests/EvalOnSelectorParityTests.cs index 4606a3d7..31fa1b84 100644 --- a/src/PlaywrightNative.Tests/EvalOnSelectorParityTests.cs +++ b/src/PlaywrightNative.Tests/EvalOnSelectorParityTests.cs @@ -16,6 +16,7 @@ */ using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -207,7 +208,7 @@ public async Task ShouldThrowErrorIfNoElementIsFound() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.EvalOnSelectorAsync("section", "e => e.id")); Assert.That(ex, Is.Not.Null); Assert.That(ex.Message, Does.Contain("Failed to find element matching selector \"section\"")); @@ -288,7 +289,7 @@ public async Task ShouldThrowOnMultipleStarCaptures() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.EvalOnSelectorAsync("*css=div >> *css=span", "e => e.outerHTML")); Assert.That(ex, Is.Not.Null); Assert.That(ex.Message, Does.Contain("Only one of the selectors can capture using * modifier")); @@ -302,7 +303,7 @@ public async Task ShouldThrowOnMalformedStarCapture() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.EvalOnSelectorAsync("*=div", "e => e.outerHTML")); Assert.That(ex, Is.Not.Null); Assert.That(ex.Message, Does.Contain("Unknown engine \"\" while parsing selector *=div")); diff --git a/src/PlaywrightNative.Tests/EvalOnSelectorStrictTests.cs b/src/PlaywrightNative.Tests/EvalOnSelectorStrictTests.cs index 39bf2c3b..ce167a64 100644 --- a/src/PlaywrightNative.Tests/EvalOnSelectorStrictTests.cs +++ b/src/PlaywrightNative.Tests/EvalOnSelectorStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.EvalOnSelectorAsync("div", "el => el.textContent", options: new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -94,7 +95,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.EvalOnSelectorAsync("div", "el => el.textContent", options: new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/EvalOnSelectorTests.cs b/src/PlaywrightNative.Tests/EvalOnSelectorTests.cs index 05136a02..d989ddab 100644 --- a/src/PlaywrightNative.Tests/EvalOnSelectorTests.cs +++ b/src/PlaywrightNative.Tests/EvalOnSelectorTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -67,7 +68,7 @@ public async Task ShouldThrowWhenNothingMatches() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("

only

").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.ThrowsAsync( + PlaywrightException ex = Assert.ThrowsAsync( () => page.EvalOnSelectorAsync(".nope", "el => el.textContent")); Assert.That(ex.Message, Does.Contain("No node found for selector")); } diff --git a/src/PlaywrightNative.Tests/ExpectTimeoutParityTests.cs b/src/PlaywrightNative.Tests/ExpectTimeoutParityTests.cs index a4935cd1..b0b5160a 100644 --- a/src/PlaywrightNative.Tests/ExpectTimeoutParityTests.cs +++ b/src/PlaywrightNative.Tests/ExpectTimeoutParityTests.cs @@ -288,7 +288,7 @@ public async Task ShouldFailLikeATimeoutWhenTheSignalIsAbortedMidAssertion() { await Page.SetContentAsync("
content
").ConfigureAwait(false); AbortController controller = new AbortController(); - Task promise = Assertions.Expect(Page.Locator("span")).ToBeVisibleAsync(new() { Timeout = 5000 }); + Task promise = Assertions.Expect(Page.Locator("span")).ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 5000, Signal = controller.Signal }); await Page.WaitForTimeoutAsync(500).ConfigureAwait(false); controller.Abort(new Exception("stop it")); Exception error = Assert.CatchAsync(() => promise); @@ -313,7 +313,7 @@ public async Task ShouldFailLikeATimeoutWhenToHaveTextIsAbortedMidAssertion() { await Page.SetContentAsync("
content
").ConfigureAwait(false); AbortController controller = new AbortController(); - Task promise = Assertions.Expect(Page.Locator("span")).ToHaveTextAsync("missing", new() { Timeout = 5000 }); + Task promise = Assertions.Expect(Page.Locator("span")).ToHaveTextAsync("missing", new LocatorAssertionsToHaveTextOptions { Timeout = 5000, Signal = controller.Signal }); await Page.WaitForTimeoutAsync(300).ConfigureAwait(false); controller.Abort(new Exception("stop it")); Exception error = Assert.CatchAsync(() => promise); @@ -328,7 +328,7 @@ public async Task ShouldFailLikeATimeoutWhenToHaveCountIsAbortedMidAssertion() { await Page.SetContentAsync("
content
").ConfigureAwait(false); AbortController controller = new AbortController(); - Task promise = Assertions.Expect(Page.Locator("span")).ToHaveCountAsync(3, new() { Timeout = 5000 }); + Task promise = Assertions.Expect(Page.Locator("span")).ToHaveCountAsync(3, new LocatorAssertionsToHaveCountOptions { Timeout = 5000, Signal = controller.Signal }); await Page.WaitForTimeoutAsync(300).ConfigureAwait(false); controller.Abort(new Exception("stop it")); Exception error = Assert.CatchAsync(() => promise); @@ -343,7 +343,7 @@ public async Task ShouldFailLikeATimeoutWhenToMatchAriaSnapshotIsAbortedMidAsser { await Page.SetContentAsync("
content
").ConfigureAwait(false); AbortController controller = new AbortController(); - Task promise = Assertions.Expect(Page.Locator("body")).ToMatchAriaSnapshotAsync("- list", new() { Timeout = 5000 }); + Task promise = Assertions.Expect(Page.Locator("body")).ToMatchAriaSnapshotAsync("- list", new LocatorAssertionsToMatchAriaSnapshotOptions { Timeout = 5000, Signal = controller.Signal }); await Page.WaitForTimeoutAsync(300).ConfigureAwait(false); controller.Abort(new Exception("stop it")); Exception error = Assert.CatchAsync(() => promise); @@ -358,7 +358,7 @@ public async Task ShouldFailLikeATimeoutWhenToHaveURLIsAbortedMidAssertion() { await Page.SetContentAsync("
content
").ConfigureAwait(false); AbortController controller = new AbortController(); - Task promise = Assertions.Expect(Page).ToHaveURLAsync("https://example.com/", new() { Timeout = 5000 }); + Task promise = Assertions.Expect(Page).ToHaveURLAsync("https://example.com/", new PageAssertionsToHaveURLOptions { Timeout = 5000, Signal = controller.Signal }); await Page.WaitForTimeoutAsync(500).ConfigureAwait(false); controller.Abort(new Exception("stop it")); Exception error = Assert.CatchAsync(() => promise); @@ -375,7 +375,7 @@ public async Task ShouldFailTheAssertionWhenTheSignalIsAlreadyAborted() { AbortController controller = new AbortController(); controller.Abort(new Exception("already aborted")); - Exception error = Assert.CatchAsync(() => Assertions.Expect(Page.Locator("div")).ToBeVisibleAsync(new() { Timeout = 5000 })); + Exception error = Assert.CatchAsync(() => Assertions.Expect(Page.Locator("div")).ToBeVisibleAsync(new LocatorAssertionsToBeVisibleOptions { Timeout = 5000, Signal = controller.Signal })); Assert.That(error.GetType().Name, Is.Not.EqualTo("AbortError")); Assert.That(MessageOf(error), Is.EqualTo(Lines(@"expect(locator).toBeVisible() failed @@ -393,7 +393,7 @@ public async Task ShouldFailTheAssertionWhenTheSignalIsAlreadyAborted() AbortController controller = new AbortController(); controller.Abort("stop it"); - Exception error = Assert.CatchAsync(() => Assertions.Expect(Page).ToHaveURLAsync(EmptyPage, new() { Timeout = 5000 })); + Exception error = Assert.CatchAsync(() => Assertions.Expect(Page).ToHaveURLAsync(EmptyPage, new PageAssertionsToHaveURLOptions { Timeout = 5000, Signal = controller.Signal })); Assert.That(error.GetType().Name, Is.Not.EqualTo("AbortError")); Assert.That(MessageOf(error), Is.EqualTo(Lines("expect(page).toHaveURL(expected) failed\n\nExpected: " + System.Text.Json.JsonSerializer.Serialize(EmptyPage) + "\nError: The assertion was aborted: stop it\n"))); } diff --git a/src/PlaywrightNative.Tests/ExposeBindingHandleTests.cs b/src/PlaywrightNative.Tests/ExposeBindingHandleTests.cs index 900419f6..4c625c30 100644 --- a/src/PlaywrightNative.Tests/ExposeBindingHandleTests.cs +++ b/src/PlaywrightNative.Tests/ExposeBindingHandleTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -69,7 +70,7 @@ public async Task ExposeBindingHandleShouldRejectMultipleArguments() await page.ExposeBindingAsync("logme", (BindingSource _, IJSHandle _) => 0).ConfigureAwait(false); await page.GoToAsync("about:blank").ConfigureAwait(false); - PlaywrightNativeException exception = Assert.ThrowsAsync( + PlaywrightException exception = Assert.ThrowsAsync( async () => await page.EvaluateAsync("window.logme({ a: 1 }, { b: 2 })").ConfigureAwait(false)); Assert.That(exception.Message, Does.Contain("exposeBindingHandle supports a single argument")); } diff --git a/src/PlaywrightNative.Tests/FillStrictTests.cs b/src/PlaywrightNative.Tests/FillStrictTests.cs index 6648f49b..3cd7f513 100644 --- a/src/PlaywrightNative.Tests/FillStrictTests.cs +++ b/src/PlaywrightNative.Tests/FillStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoInputsMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.FillAsync("input", "wave644", strict: true)); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameFillShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.FillAsync("input", "wave644", strict: true)); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/FocusStrictTests.cs b/src/PlaywrightNative.Tests/FocusStrictTests.cs index a902766b..fad9436c 100644 --- a/src/PlaywrightNative.Tests/FocusStrictTests.cs +++ b/src/PlaywrightNative.Tests/FocusStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.FocusAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.FocusAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/FrameActionTests.cs b/src/PlaywrightNative.Tests/FrameActionTests.cs index 14c02381..9b4a5a4d 100644 --- a/src/PlaywrightNative.Tests/FrameActionTests.cs +++ b/src/PlaywrightNative.Tests/FrameActionTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -185,7 +186,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameEvaluateTests.cs b/src/PlaywrightNative.Tests/FrameEvaluateTests.cs index 6abd776e..8d41024a 100644 --- a/src/PlaywrightNative.Tests/FrameEvaluateTests.cs +++ b/src/PlaywrightNative.Tests/FrameEvaluateTests.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -177,7 +178,7 @@ public async Task ShouldNotAllowCrossFrameJsHandles() JsonElement childResult = await childFrame.EvaluateAsync("(() => window['__foo'])()").ConfigureAwait(false); Assert.That(childResult.GetProperty("bar").GetString(), Is.EqualTo("baz")); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => childFrame.EvaluateAsync("foo => foo.bar", handle)); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("JSHandles can be evaluated only in the context they were created!")); @@ -213,7 +214,7 @@ public async Task ShouldNotAllowCrossFrameElementHandlesWhenFramesDoNotScriptEac await page.GoToAsync(EmptyPage).ConfigureAwait(false); IFrame frame = await AttachFrameAsync(page, "frame1", CrossProcessPrefix + "/empty.html").ConfigureAwait(false); IElementHandle bodyHandle = await frame.QuerySelectorAsync("body").ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => page.EvaluateAsync("body => body.innerHTML", bodyHandle)); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Unable to adopt element handle from a different document")); @@ -232,7 +233,7 @@ public async Task ShouldThrowForDetachedFrames() IFrame frame1 = await AttachFrameAsync(page, "frame1", EmptyPage).ConfigureAwait(false); await DetachFrameAsync(page, "frame1").ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => frame1.EvaluateAsync("(() => 7 * 8)()")); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("frame.evaluate: Frame was detached")); diff --git a/src/PlaywrightNative.Tests/FrameFrameElementParityTests.cs b/src/PlaywrightNative.Tests/FrameFrameElementParityTests.cs index fa864e85..be270263 100644 --- a/src/PlaywrightNative.Tests/FrameFrameElementParityTests.cs +++ b/src/PlaywrightNative.Tests/FrameFrameElementParityTests.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -222,7 +223,7 @@ public async Task ShouldThrowWhenDetached() await page.GoToAsync(EmptyPage).ConfigureAwait(false); IFrame frame1 = await AttachFrameAsync(page, "frame1", EmptyPage).ConfigureAwait(false); await page.EvalOnSelectorAsync("#frame1", "e => e.remove()").ConfigureAwait(false); - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( () => frame1.FrameElementAsync()); Assert.That(error.Message, Does.Contain("Frame has been detached.")); } diff --git a/src/PlaywrightNative.Tests/FrameGetByTests.cs b/src/PlaywrightNative.Tests/FrameGetByTests.cs index d1c818c5..958f2d88 100644 --- a/src/PlaywrightNative.Tests/FrameGetByTests.cs +++ b/src/PlaywrightNative.Tests/FrameGetByTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -177,7 +178,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameLocatorGetByTests.cs b/src/PlaywrightNative.Tests/FrameLocatorGetByTests.cs index 0521b6c8..5fa32772 100644 --- a/src/PlaywrightNative.Tests/FrameLocatorGetByTests.cs +++ b/src/PlaywrightNative.Tests/FrameLocatorGetByTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -78,7 +79,7 @@ public async Task GetByRoleShouldThrowWhenTwoMatch() await page.SetContentAsync( "").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.FrameLocator("iframe").GetByRole("button").ClickAsync()); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/FrameLocatorLocatorTests.cs b/src/PlaywrightNative.Tests/FrameLocatorLocatorTests.cs index 739277d1..4555f8c8 100644 --- a/src/PlaywrightNative.Tests/FrameLocatorLocatorTests.cs +++ b/src/PlaywrightNative.Tests/FrameLocatorLocatorTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -96,7 +97,7 @@ public async Task FrameLocatorShouldRejectAnotherPage() await page.SetContentAsync("").ConfigureAwait(false); await other.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.Throws( + PlaywrightException ex = Assert.Throws( () => page.FrameLocator("iframe").Locator(other.Locator("input"))); Assert.That(ex.Message, Does.Contain("same frame")); diff --git a/src/PlaywrightNative.Tests/FrameLocatorTests.cs b/src/PlaywrightNative.Tests/FrameLocatorTests.cs index 0c55fd08..40ad0f17 100644 --- a/src/PlaywrightNative.Tests/FrameLocatorTests.cs +++ b/src/PlaywrightNative.Tests/FrameLocatorTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,11 +37,11 @@ public async Task FrameLocatorShouldClickInsideIframe() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); IFrame child = await AttachBlankChildFrameAsync(page).ConfigureAwait(false); - await child.SetContentAsync("").ConfigureAwait(false); + await child.SetContentAsync("").ConfigureAwait(false); await page.FrameLocator("iframe").Locator("button").ClickAsync().ConfigureAwait(false); - string id = await child.EvaluateAsync("document.activeElement && document.activeElement.id").ConfigureAwait(false); + string id = await child.EvaluateAsync("window.lastClickedId").ConfigureAwait(false); Assert.That(id, Is.EqualTo("inner")); Assert.That(page.FrameLocator("iframe").Owner.Frame, Is.SameAs(page.MainFrame)); } @@ -77,7 +78,7 @@ await page.SetContentAsync( "" + "").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.FrameLocator("iframe").Locator("button").ClickAsync()); Assert.That(ex, Is.Not.Null); @@ -94,11 +95,11 @@ public async Task ContentFrameShouldEnterIframeLocator() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); IFrame child = await AttachBlankChildFrameAsync(page).ConfigureAwait(false); - await child.SetContentAsync("").ConfigureAwait(false); + await child.SetContentAsync("").ConfigureAwait(false); await page.Locator("iframe").ContentFrame.Locator("button").ClickAsync().ConfigureAwait(false); - string id = await child.EvaluateAsync("document.activeElement && document.activeElement.id").ConfigureAwait(false); + string id = await child.EvaluateAsync("window.lastClickedId").ConfigureAwait(false); Assert.That(id, Is.EqualTo("inner")); } @@ -180,7 +181,7 @@ private static async Task WaitForFrameReadyAsync(IFrame frame) return; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameWaitForFunctionTests.cs b/src/PlaywrightNative.Tests/FrameWaitForFunctionTests.cs index 459e83c4..1225cfbf 100644 --- a/src/PlaywrightNative.Tests/FrameWaitForFunctionTests.cs +++ b/src/PlaywrightNative.Tests/FrameWaitForFunctionTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -114,7 +115,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameWaitForLoadStateTests.cs b/src/PlaywrightNative.Tests/FrameWaitForLoadStateTests.cs index d55af8d0..4aca31e6 100644 --- a/src/PlaywrightNative.Tests/FrameWaitForLoadStateTests.cs +++ b/src/PlaywrightNative.Tests/FrameWaitForLoadStateTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -127,7 +128,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameWaitForNavigationTests.cs b/src/PlaywrightNative.Tests/FrameWaitForNavigationTests.cs index ef76accf..d14cb529 100644 --- a/src/PlaywrightNative.Tests/FrameWaitForNavigationTests.cs +++ b/src/PlaywrightNative.Tests/FrameWaitForNavigationTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -179,7 +180,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameWaitForSelectorTests.cs b/src/PlaywrightNative.Tests/FrameWaitForSelectorTests.cs index d3a33491..19726dcf 100644 --- a/src/PlaywrightNative.Tests/FrameWaitForSelectorTests.cs +++ b/src/PlaywrightNative.Tests/FrameWaitForSelectorTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -116,7 +117,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/FrameWaitForUrlTests.cs b/src/PlaywrightNative.Tests/FrameWaitForUrlTests.cs index 39f5030f..31f53fe0 100644 --- a/src/PlaywrightNative.Tests/FrameWaitForUrlTests.cs +++ b/src/PlaywrightNative.Tests/FrameWaitForUrlTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -122,7 +123,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/GetAttributeStrictTests.cs b/src/PlaywrightNative.Tests/GetAttributeStrictTests.cs index f15a4ca3..3758186c 100644 --- a/src/PlaywrightNative.Tests/GetAttributeStrictTests.cs +++ b/src/PlaywrightNative.Tests/GetAttributeStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.GetAttributeAsync("div", "data-x", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.GetAttributeAsync("div", "data-x", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/GlobalUsings.MicrosoftPlaywright.cs b/src/PlaywrightNative.Tests/GlobalUsings.MicrosoftPlaywright.cs index 29f8aea3..35c6db76 100644 --- a/src/PlaywrightNative.Tests/GlobalUsings.MicrosoftPlaywright.cs +++ b/src/PlaywrightNative.Tests/GlobalUsings.MicrosoftPlaywright.cs @@ -92,6 +92,10 @@ global using KeyboardModifier = Microsoft.Playwright.KeyboardModifier; global using LoadState = Microsoft.Playwright.LoadState; global using Location = Microsoft.Playwright.Location; +global using LocatorAssertionsToBeVisibleOptions = PlaywrightNative.Compat.LegacyLocatorAssertionsToBeVisibleOptions; +global using LocatorAssertionsToHaveCountOptions = PlaywrightNative.Compat.LegacyLocatorAssertionsToHaveCountOptions; +global using LocatorAssertionsToHaveTextOptions = PlaywrightNative.Compat.LegacyLocatorAssertionsToHaveTextOptions; +global using LocatorAssertionsToMatchAriaSnapshotOptions = PlaywrightNative.Compat.LegacyLocatorAssertionsToMatchAriaSnapshotOptions; global using LocatorClickOptions = PlaywrightNative.Compat.LegacyLocatorClickOptions; global using LocatorDblClickOptions = Microsoft.Playwright.LocatorDblClickOptions; global using LocatorHoverOptions = PlaywrightNative.Compat.LegacyLocatorHoverOptions; @@ -99,6 +103,7 @@ global using Margin = Microsoft.Playwright.Margin; global using Media = Microsoft.Playwright.Media; global using MouseButton = Microsoft.Playwright.MouseButton; +global using PageAssertionsToHaveURLOptions = PlaywrightNative.Compat.LegacyPageAssertionsToHaveURLOptions; global using PageClickOptions = PlaywrightNative.Compat.LegacyPageClickOptions; global using PageDragAndDropOptions = PlaywrightNative.Compat.LegacyPageDragAndDropOptions; global using PageFocusOptions = PlaywrightNative.Compat.LegacyPageFocusOptions; diff --git a/src/PlaywrightNative.Tests/GoToAndFrameHandleTests.cs b/src/PlaywrightNative.Tests/GoToAndFrameHandleTests.cs index 1576f761..ade7e69d 100644 --- a/src/PlaywrightNative.Tests/GoToAndFrameHandleTests.cs +++ b/src/PlaywrightNative.Tests/GoToAndFrameHandleTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -168,7 +169,7 @@ await page.EvaluateAsync(@" return child; } } - catch (PlaywrightNativeException) + catch (PlaywrightException) { // Execution context is not ready yet. } diff --git a/src/PlaywrightNative.Tests/HoverStrictTests.cs b/src/PlaywrightNative.Tests/HoverStrictTests.cs index 11a9b34e..c0f5fda3 100644 --- a/src/PlaywrightNative.Tests/HoverStrictTests.cs +++ b/src/PlaywrightNative.Tests/HoverStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.HoverAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.HoverAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/InnerHTMLStrictTests.cs b/src/PlaywrightNative.Tests/InnerHTMLStrictTests.cs index 3088f64f..3ff39f3e 100644 --- a/src/PlaywrightNative.Tests/InnerHTMLStrictTests.cs +++ b/src/PlaywrightNative.Tests/InnerHTMLStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.InnerHTMLAsync("div", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.InnerHTMLAsync("div", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/InnerTextStrictTests.cs b/src/PlaywrightNative.Tests/InnerTextStrictTests.cs index c7bb5e95..d98d4dae 100644 --- a/src/PlaywrightNative.Tests/InnerTextStrictTests.cs +++ b/src/PlaywrightNative.Tests/InnerTextStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("onetwo").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.InnerTextAsync("span", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("onetwo").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.InnerTextAsync("span", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/InputValueStrictTests.cs b/src/PlaywrightNative.Tests/InputValueStrictTests.cs index 55a66fe4..d511b8d5 100644 --- a/src/PlaywrightNative.Tests/InputValueStrictTests.cs +++ b/src/PlaywrightNative.Tests/InputValueStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoInputsMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.InputValueAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -94,7 +95,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.InputValueAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/InterceptionParityTests.cs b/src/PlaywrightNative.Tests/InterceptionParityTests.cs index b27852a7..076a93cf 100644 --- a/src/PlaywrightNative.Tests/InterceptionParityTests.cs +++ b/src/PlaywrightNative.Tests/InterceptionParityTests.cs @@ -20,6 +20,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Helpers; using PlaywrightNative.NUnit; @@ -276,21 +277,21 @@ public void ShouldWorkWithGlob() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowOnUnbalancedGlobBraces() { - Exception unmatchedOpen = Assert.Throws(() => UrlMatcher.GlobToRegexPattern("{foo")); + Exception unmatchedOpen = Assert.Throws(() => UrlMatcher.GlobToRegexPattern("{foo")); Assert.That(unmatchedOpen.Message, Does.Contain("Invalid glob pattern \"{foo\": unmatched '{'")); - Exception unmatchedClose = Assert.Throws(() => UrlMatcher.GlobToRegexPattern("}foo")); + Exception unmatchedClose = Assert.Throws(() => UrlMatcher.GlobToRegexPattern("}foo")); Assert.That(unmatchedClose.Message, Does.Contain("Invalid glob pattern \"}foo\": unmatched '}'")); Assert.That( - Assert.Throws(() => UrlMatcher.GlobToRegexPattern("http://*/foo{")).Message, + Assert.Throws(() => UrlMatcher.GlobToRegexPattern("http://*/foo{")).Message, Does.Contain("unmatched '{'")); Assert.That( - Assert.Throws(() => UrlMatcher.GlobToRegexPattern("**/*.png?{")).Message, + Assert.Throws(() => UrlMatcher.GlobToRegexPattern("**/*.png?{")).Message, Does.Contain("unmatched '{'")); Assert.That( - Assert.Throws(() => UrlMatcher.GlobToRegexPattern("https://example.com/{a")).Message, + Assert.Throws(() => UrlMatcher.GlobToRegexPattern("https://example.com/{a")).Message, Does.Contain("unmatched '{'")); Assert.That( - Assert.Throws(() => UrlMatcher.GlobToRegexPattern("{{foo}")).Message, + Assert.Throws(() => UrlMatcher.GlobToRegexPattern("{{foo}")).Message, Does.Contain("nested '{' is not supported")); Assert.DoesNotThrow(() => UrlMatcher.GlobToRegexPattern("\\{foo")); Assert.DoesNotThrow(() => UrlMatcher.GlobToRegexPattern("foo\\}")); diff --git a/src/PlaywrightNative.Tests/IsCheckedStrictTests.cs b/src/PlaywrightNative.Tests/IsCheckedStrictTests.cs index c9bd7d5e..c25c649f 100644 --- a/src/PlaywrightNative.Tests/IsCheckedStrictTests.cs +++ b/src/PlaywrightNative.Tests/IsCheckedStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.IsCheckedAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.IsCheckedAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/IsDisabledStrictTests.cs b/src/PlaywrightNative.Tests/IsDisabledStrictTests.cs index 684c4994..7f82063d 100644 --- a/src/PlaywrightNative.Tests/IsDisabledStrictTests.cs +++ b/src/PlaywrightNative.Tests/IsDisabledStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.IsDisabledAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.IsDisabledAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/IsEditableStrictTests.cs b/src/PlaywrightNative.Tests/IsEditableStrictTests.cs index fa5a5db7..877d61da 100644 --- a/src/PlaywrightNative.Tests/IsEditableStrictTests.cs +++ b/src/PlaywrightNative.Tests/IsEditableStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.IsEditableAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.IsEditableAsync("input", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/IsEnabledStrictTests.cs b/src/PlaywrightNative.Tests/IsEnabledStrictTests.cs index f1e6a127..29fdf330 100644 --- a/src/PlaywrightNative.Tests/IsEnabledStrictTests.cs +++ b/src/PlaywrightNative.Tests/IsEnabledStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.IsEnabledAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -96,7 +97,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.IsEnabledAsync("button", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/IsHiddenStrictTests.cs b/src/PlaywrightNative.Tests/IsHiddenStrictTests.cs index f7f3284c..65041d3d 100644 --- a/src/PlaywrightNative.Tests/IsHiddenStrictTests.cs +++ b/src/PlaywrightNative.Tests/IsHiddenStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.IsHiddenAsync("div", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -94,7 +95,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.IsHiddenAsync("div", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/IsVisibleStrictTests.cs b/src/PlaywrightNative.Tests/IsVisibleStrictTests.cs index 0ab93e6f..0846c3c4 100644 --- a/src/PlaywrightNative.Tests/IsVisibleStrictTests.cs +++ b/src/PlaywrightNative.Tests/IsVisibleStrictTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -36,7 +37,7 @@ public async Task StrictTrueShouldThrowWhenTwoNodesMatch() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.IsVisibleAsync("div", new() { Strict = true })); Assert.That(ex, Is.Not.Null); @@ -94,7 +95,7 @@ public async Task FrameShouldHonorStrict() Assert.That(frame, Is.Not.Null); await frame.SetContentAsync("
one
two
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => frame.IsVisibleAsync("div", new() { Strict = true })); Assert.That(ex, Is.Not.Null); diff --git a/src/PlaywrightNative.Tests/LaunchChannelTests.cs b/src/PlaywrightNative.Tests/LaunchChannelTests.cs index d459cba7..1981125c 100644 --- a/src/PlaywrightNative.Tests/LaunchChannelTests.cs +++ b/src/PlaywrightNative.Tests/LaunchChannelTests.cs @@ -17,6 +17,7 @@ using System; using System.IO; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Helpers; using PlaywrightNative.NUnit; @@ -54,11 +55,16 @@ public void ResolverShouldHandleEdge() if (installed) { string path = BrowserChannelResolver.Resolve(BrowserChannel.Msedge); - Assert.That(path, Does.Contain("msedge").IgnoreCase); + // Linux/Windows install layouts use "msedge" in the path; macOS + // ships the official app as "Microsoft Edge.app/.../Microsoft Edge". + Assert.That( + path, + Does.Contain("msedge").IgnoreCase + .Or.Contain("Microsoft Edge").IgnoreCase); return; } - PlaywrightNativeException exception = Assert.Catch(() => BrowserChannelResolver.Resolve(BrowserChannel.Msedge)) as PlaywrightNativeException; + PlaywrightException exception = Assert.Catch(() => BrowserChannelResolver.Resolve(BrowserChannel.Msedge)) as PlaywrightException; Assert.That(exception, Is.Not.Null); Assert.That(exception.Message, Does.Contain("msedge")); } diff --git a/src/PlaywrightNative.Tests/LaunchPersistentStrictSelectorsTests.cs b/src/PlaywrightNative.Tests/LaunchPersistentStrictSelectorsTests.cs index 31b82a3c..845d4128 100644 --- a/src/PlaywrightNative.Tests/LaunchPersistentStrictSelectorsTests.cs +++ b/src/PlaywrightNative.Tests/LaunchPersistentStrictSelectorsTests.cs @@ -17,6 +17,7 @@ using System; using System.IO; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -75,7 +76,7 @@ public async Task LaunchPersistentContextAsyncShouldHonorStrictSelectors() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
").ConfigureAwait(false); - PlaywrightNativeException ex = Assert.CatchAsync( + PlaywrightException ex = Assert.CatchAsync( () => page.ClickAsync("button")); Assert.That(context.StrictSelectors, Is.True); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextAddCookiesParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextAddCookiesParityTests.cs index a7207dc3..16d07930 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextAddCookiesParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextAddCookiesParityTests.cs @@ -988,17 +988,24 @@ private static async Task DisposeQuietlyAsync(IAsyncDisposable disposable) private static SameSiteAttribute DefaultSameSiteCookieValue() { - if (TestConstants.IsWebKit && TestConstants.IsWindows) + // Upstream defaultSameSiteCookieValue: Chromium and WebKit/Linux are Lax; + // WebKit on Windows and older macOS (mac14 bots) report None; Firefox is None. + if (TestConstants.IsChromium) { - return SameSiteAttribute.None; + return SameSiteAttribute.Lax; + } + + if (TestConstants.IsWebKit && TestConstants.IsLinux) + { + return SameSiteAttribute.Lax; } - if (TestConstants.IsFirefox) + if (TestConstants.IsWebKit) { return SameSiteAttribute.None; } - return SameSiteAttribute.Lax; + return SameSiteAttribute.None; } private static SameSiteAttribute SameSiteLaxOrWindowsNone() diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextClearCookiesParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextClearCookiesParityTests.cs index ecbe464d..8674ba1b 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextClearCookiesParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextClearCookiesParityTests.cs @@ -361,6 +361,13 @@ public async Task ShouldNotTransientlyDeleteNonMatchingCookiesWhenFiltering() Assert.Ignore("cookieStore change events not supported on WebKit/Windows (curl backend lacks cookie change notifications)"); } + // Official it.skip(isFrozenWebkit): macOS < 15 (and some old Linux hosts) ship a + // frozen WebKit build without the Cookie Store API (cookieStore is undefined). + if (TestConstants.IsWebKit && IsFrozenWebKitHost()) + { + Assert.Ignore("cookieStore is unavailable on frozen WebKit (macOS < 15 / legacy hosts)"); + } + string hostname = new Uri(Prefix).Host; await _context.AddCookiesAsync(new[] { @@ -506,6 +513,38 @@ private static void EnsureServer() } } + /// + /// Matches upstream isFrozenWebkit: WebKit hosts that pin an older + /// browser build without Cookie Store (macOS < 15). + /// + /// when Cookie Store is expected to be missing. + private static bool IsFrozenWebKitHost() + { + if (!OperatingSystem.IsMacOS()) + { + return false; + } + + // Prefer the BrowserData platform key (mac14* always ships frozen revision 2251). + try + { + string platformKey = BrowserData.PlaywrightPlatformKey( + SupportedBrowser.Webkit, + BrowserData.CurrentPlatform()); + if (platformKey.StartsWith("mac14", StringComparison.Ordinal)) + { + return true; + } + } + catch (ArgumentException) + { + } + + // Darwin 23 == macOS 14.x; Darwin 24+ == macOS 15+. + Version version = Environment.OSVersion.Version; + return version.Major == 23; + } + private static void ServeHtml(string path) { Server.SetRoute(path, async http => diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesParityTests.cs index c762b419..1badac60 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesParityTests.cs @@ -218,6 +218,10 @@ public async Task ShouldAllowAddingCookiesWithMoreThan400DaysExpiration() { EnsureServer(); double expire = (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000d) + (401d * 24 * 3600); + // Cookie.Expires is float32 (Microsoft.Playwright). Upstream Node compares the + // same JS number used for addCookies; after the float cast we must compare + // against that float — (float)expire can round above the original double. + float expireFloat = (float)expire; await _context.AddCookiesAsync(new[] { new Cookie @@ -226,7 +230,7 @@ await _context.AddCookiesAsync(new[] Value = "John Doe", Domain = Hostname, Path = "/", - Expires = (float?)expire, + Expires = expireFloat, HttpOnly = false, Secure = false, SameSite = SameSiteAttribute.Lax, @@ -240,7 +244,7 @@ await _context.AddCookiesAsync(new[] Assert.That(cookies[0].Domain, Is.EqualTo(Hostname)); Assert.That(cookies[0].Path, Is.EqualTo("/")); Assert.That(cookies[0].Expires, Is.GreaterThan(0d)); - Assert.That(cookies[0].Expires, Is.LessThanOrEqualTo(expire)); + Assert.That(cookies[0].Expires, Is.LessThanOrEqualTo(expireFloat)); } [PlaywrightTest("browsercontext-cookies.spec.ts", "should properly report httpOnly cookie")] @@ -532,7 +536,9 @@ await _context.AddCookiesAsync(new[] Name = "doggo", Value = "woofs", SameSite = SameSiteAttribute.None, - Expires = 253402300800, + // float32 cannot represent Max+1 (253402300800) distinctly from Max; + // use the next representable float above (float)Max so overflow still throws. + Expires = 253402316800f, }, }).ConfigureAwait(false); } @@ -666,7 +672,7 @@ await _page.EvaluateAsync("() => document.cookie").ConfigureAwait(false) Assert.That(cookies[0].Value, Is.EqualTo("value1")); Assert.That(cookies[0].Domain, Is.EqualTo(Hostname)); Assert.That(cookies[0].Path, Is.EqualTo("/")); - Assert.That(cookies[0].Expires, Is.TypeOf()); + Assert.That(cookies[0].Expires, Is.TypeOf()); Assert.That(cookies[0].HttpOnly, Is.False); Assert.That(cookies[0].Secure, Is.False); Assert.That(cookies[0].SameSite, Is.EqualTo(DefaultSameSiteCookieValue())); @@ -758,17 +764,24 @@ private static async Task DisposeQuietlyAsync(IAsyncDisposable disposable) private static SameSiteAttribute DefaultSameSiteCookieValue() { - if (TestConstants.IsWebKit && TestConstants.IsWindows) + // Upstream defaultSameSiteCookieValue: Chromium and WebKit/Linux are Lax; + // WebKit on Windows and older macOS (mac14 bots) report None; Firefox is None. + if (TestConstants.IsChromium) { - return SameSiteAttribute.None; + return SameSiteAttribute.Lax; } - if (TestConstants.IsFirefox) + if (TestConstants.IsWebKit && TestConstants.IsLinux) + { + return SameSiteAttribute.Lax; + } + + if (TestConstants.IsWebKit) { return SameSiteAttribute.None; } - return SameSiteAttribute.Lax; + return SameSiteAttribute.None; } private static SameSiteAttribute SameSiteLaxOrWindowsNone() diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesThirdPartyParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesThirdPartyParityTests.cs index e835f068..38aaf13c 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesThirdPartyParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextCookiesThirdPartyParityTests.cs @@ -26,6 +26,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using NUnit.Framework; +using PlaywrightNative.Helpers; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -220,20 +221,35 @@ public async Task AddPartitionedCookieViaApi() IBrowserContext context = await NewHttpsContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); AddCommonCookieHandlers(); + Cookie topLevelPartitioned = new Cookie + { + Name = "top-level-partitioned", + Value = "value", + Domain = HttpsHostname, + Path = "/", + Expires = -1, + HttpOnly = false, + Secure = true, + SameSite = SameSiteAttribute.None, + PartitionKey = "https://localhost", + }; + CookieExtras.SetHasCrossSiteAncestor(topLevelPartitioned, false); + Cookie framePartitioned = new Cookie + { + Name = "frame-partitioned", + Value = "value", + Domain = HttpsHostname, + Path = "/", + Expires = -1, + HttpOnly = false, + Secure = true, + SameSite = SameSiteAttribute.None, + PartitionKey = "https://127.0.0.1", + }; + CookieExtras.SetHasCrossSiteAncestor(framePartitioned, true); await context.AddCookiesAsync(new[] { - new Cookie - { - Name = "top-level-partitioned", - Value = "value", - Domain = HttpsHostname, - Path = "/", - Expires = -1, - HttpOnly = false, - Secure = true, - SameSite = SameSiteAttribute.None, - PartitionKey = "https://localhost", - }, + topLevelPartitioned, new Cookie { Name = "top-level-non-partitioned", @@ -245,18 +261,7 @@ await context.AddCookiesAsync(new[] Secure = true, SameSite = SameSiteAttribute.None, }, - new Cookie - { - Name = "frame-partitioned", - Value = "value", - Domain = HttpsHostname, - Path = "/", - Expires = -1, - HttpOnly = false, - Secure = true, - SameSite = SameSiteAttribute.None, - PartitionKey = "https://127.0.0.1", - }, + framePartitioned, new Cookie { Name = "frame-non-partitioned", @@ -925,7 +930,7 @@ private static List ToSetCookies(IReadOnlyList list = new List(); foreach (BrowserContextCookiesResult cookie in cookies) { - list.Add(new Cookie + Cookie mapped = new Cookie { Name = cookie.Name, Value = cookie.Value, @@ -936,7 +941,11 @@ private static List ToSetCookies(IReadOnlyList( + PlaywrightException exception = Assert.ThrowsAsync( async () => await page.EvaluateAsync( "(async function() { return await window['compute'](9, 4); })()").ConfigureAwait(false)); Assert.That(exception.Message, Does.Contain("is not a function")); @@ -192,15 +193,15 @@ public async Task ShouldThrowForDuplicateRegistrations() IBrowserContext context = await _browser.NewContextAsync().ConfigureAwait(false); await context.ExposeFunctionAsync("foo", () => { }).ConfigureAwait(false); await context.ExposeFunctionAsync("bar", () => { }).ConfigureAwait(false); - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( async () => await context.ExposeFunctionAsync("foo", () => { }).ConfigureAwait(false)); Assert.That(error.Message, Does.Contain("Function \"foo\" has been already registered")); IPage page = await context.NewPageAsync().ConfigureAwait(false); - error = Assert.ThrowsAsync( + error = Assert.ThrowsAsync( async () => await page.ExposeFunctionAsync("foo", () => { }).ConfigureAwait(false)); Assert.That(error.Message, Does.Contain("Function \"foo\" has been already registered in the browser context")); await page.ExposeFunctionAsync("baz", () => { }).ConfigureAwait(false); - error = Assert.ThrowsAsync( + error = Assert.ThrowsAsync( async () => await context.ExposeFunctionAsync("baz", () => { }).ConfigureAwait(false)); Assert.That(error.Message, Does.Contain("Function \"baz\" has been already registered in one of the pages")); await context.CloseAsync().ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextPagesParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextPagesParityTests.cs index d32b292f..59ca11bd 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextPagesParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextPagesParityTests.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -318,7 +319,7 @@ public async Task ShouldClosePageWhileAReloadIsCommitting() for (int i = 0; i < 10; i++) { IPage page = await context.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => page.EvaluateAsync(ReloadNeverSettles)); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("navigation")); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextRouteParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextRouteParityTests.cs index ff72d1a4..a1287d06 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextRouteParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextRouteParityTests.cs @@ -708,12 +708,24 @@ await context.RouteAsync("**/*", async route => private static SameSiteAttribute DefaultSameSite() { - if (TestConstants.IsWebKit && TestConstants.IsWindows) + // Upstream defaultSameSiteCookieValue: Chromium and WebKit/Linux are Lax; + // WebKit on Windows and older macOS report None; Firefox is None. + if (TestConstants.IsChromium) + { + return SameSiteAttribute.Lax; + } + + if (TestConstants.IsWebKit && TestConstants.IsLinux) + { + return SameSiteAttribute.Lax; + } + + if (TestConstants.IsWebKit) { return SameSiteAttribute.None; } - return SameSiteAttribute.Lax; + return SameSiteAttribute.None; } private static void AssertCookie( diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextSetExtraHttpHeadersParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextSetExtraHttpHeadersParityTests.cs index 6f43292b..6cfc70d0 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextSetExtraHttpHeadersParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextSetExtraHttpHeadersParityTests.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -142,7 +143,7 @@ await page.SetExtraHttpHeadersAsync(new Dictionary [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowForNonStringHeaderValues() { - PlaywrightNativeException error3 = Assert.CatchAsync( + PlaywrightException error3 = Assert.CatchAsync( () => _browser.NewContextAsync(new() { ExtraHTTPHeaders = new Dictionary { ["foo"] = null } })); Assert.That(error3.Message, Does.Contain("Expected value of header \"foo\" to be String, but \"object\" is found.")); } diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextStorageStateParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextStorageStateParityTests.cs index 38591490..1b7641ce 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextStorageStateParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextStorageStateParityTests.cs @@ -21,6 +21,7 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Helpers; using PlaywrightNative.NUnit; @@ -175,7 +176,7 @@ await page.RouteAsync("**/*", route => [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldReportGoodErrorIfTheUrlIsNotValid() { - PlaywrightNativeException error = Assert.CatchAsync(() => + PlaywrightException error = Assert.CatchAsync(() => _browser.NewContextAsync(new() { StorageState = "{\"cookies\":[],\"origins\":[{\"origin\":\"foo\",\"localStorage\":[{\"name\":\"name1\",\"value\":\"value1\"}]}]}" })); Assert.That(error.Message, Does.Contain("Error setting storage state:")); Assert.That(error.Message, Does.Contain("foo")); @@ -284,7 +285,7 @@ await page.RouteAsync("**/*", route => public void ShouldHandleMissingFile() { string file = Path.Combine(Path.GetTempPath(), "pwsharp-does-not-exist-" + Guid.NewGuid().ToString("N") + ".json"); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => _browser.NewContextAsync(new BrowserContextOptions { StorageStatePath = file })); Assert.That(error.Message, Does.Contain("Error reading storage state from " + file + ":\nENOENT")); } @@ -298,7 +299,7 @@ public void ShouldHandleMalformedFile() File.WriteAllText(file, "not-json"); try { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => _browser.NewContextAsync(new BrowserContextOptions { StorageStatePath = file })); Assert.That( error.Message, @@ -689,7 +690,7 @@ public async Task SetStorageStateShouldHandleMissingFile() { IBrowserContext context = await _browser.NewContextAsync().ConfigureAwait(false); string file = Path.Combine(Path.GetTempPath(), "pwsharp-does-not-exist-" + Guid.NewGuid().ToString("N") + ".json"); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => context.SetStorageStateAsync(storageStatePath: file)); Assert.That(error.Message, Does.Contain("Error reading storage state from " + file)); await context.CloseAsync().ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextStrictParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextStrictParityTests.cs index 646c4680..fb7ac7de 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextStrictParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextStrictParityTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -71,7 +72,7 @@ public async Task ShouldNotFailPageTextContentInNonStrictMode() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldFailPageTextContentInStrictMode() { - PlaywrightNativeException error = Assert.CatchAsync(async () => + PlaywrightException error = Assert.CatchAsync(async () => { IBrowserContext context = await _browser.NewContextAsync(new BrowserContextOptions { StrictSelectors = true }).ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); @@ -86,7 +87,7 @@ public void ShouldFailPageTextContentInStrictMode() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldFailPageClickInStrictMode() { - PlaywrightNativeException error = Assert.CatchAsync(async () => + PlaywrightException error = Assert.CatchAsync(async () => { IBrowserContext context = await _browser.NewContextAsync(new BrowserContextOptions { StrictSelectors = true }).ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextTimezoneIdParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextTimezoneIdParityTests.cs index b71bbcbd..44c327af 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextTimezoneIdParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextTimezoneIdParityTests.cs @@ -17,6 +17,7 @@ using System; using System.Globalization; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -129,13 +130,13 @@ public async Task ShouldThrowForInvalidTimezoneIDsWhenCreatingPages() foreach (string timezoneId in new[] { "Foo/Bar", "Baz/Qux" }) { IBrowserContext context = null; - PlaywrightNativeException error = null; + PlaywrightException error = null; try { context = await _browser.NewContextAsync(new() { TimezoneId = timezoneId }).ConfigureAwait(false); await context.NewPageAsync().ConfigureAwait(false); } - catch (PlaywrightNativeException ex) + catch (PlaywrightException ex) { error = ex; } diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextViewportMobileParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextViewportMobileParityTests.cs index 508aa011..66f563eb 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextViewportMobileParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextViewportMobileParityTests.cs @@ -18,6 +18,7 @@ using System.Globalization; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -366,7 +367,7 @@ public async Task ShouldScrollWhenEmulatingAMobileViewport() await page.Mouse.MoveAsync(50, 60).ConfigureAwait(false); if (TestConstants.IsWebKit) { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => page.Mouse.WheelAsync(0, 100)); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Mouse wheel is not supported in mobile WebKit")); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserContextViewportParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserContextViewportParityTests.cs index d1074f06..aec890a9 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserContextViewportParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserContextViewportParityTests.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -266,10 +267,10 @@ public async Task ShouldThrowOnTapIfHasTouchIsNotEnabled() IBrowserContext context = await _browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.SetContentAsync("
a
").ConfigureAwait(false); - PlaywrightNativeException pageError = Assert.CatchAsync(() => page.TapAsync("div")); + PlaywrightException pageError = Assert.CatchAsync(() => page.TapAsync("div")); Assert.That(pageError, Is.Not.Null); Assert.That(pageError.Message, Does.Contain("The page does not support tap")); - PlaywrightNativeException locatorError = Assert.CatchAsync(() => page.Locator("div").TapAsync()); + PlaywrightException locatorError = Assert.CatchAsync(() => page.Locator("div").TapAsync()); Assert.That(locatorError, Is.Not.Null); Assert.That(locatorError.Message, Does.Contain("The page does not support tap")); await context.CloseAsync().ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserParityTests.cs index 7651fc7d..e08d38aa 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserParityTests.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -93,7 +94,7 @@ public async Task ShouldCreateNewPage() public async Task ShouldThrowUponSecondCreateNewPage() { IPage page = await _browser.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => page.Context.NewPageAsync()); await page.CloseAsync().ConfigureAwait(false); Assert.That(error.Message, Does.Contain("Please use browser.newContext()")); diff --git a/src/PlaywrightNative.Tests/LibraryBrowserTypeBasicParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserTypeBasicParityTests.cs index 5c57c5f0..619475fa 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserTypeBasicParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserTypeBasicParityTests.cs @@ -15,6 +15,7 @@ * limitations under the License. */ using System.IO; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -61,7 +62,7 @@ public void ShouldThrowWhenTryingToConnectWithNotChromium() Assert.Ignore("official skip: browserName === 'chromium' || browserName === 'webkit'"); } - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => CurrentBrowserType().ConnectOverCDPAsync("ws://foo")); Assert.That(error.Message, Is.EqualTo("Connecting over CDP is only supported in Chromium and WebKit.")); } diff --git a/src/PlaywrightNative.Tests/LibraryBrowserTypeLaunchParityTests.cs b/src/PlaywrightNative.Tests/LibraryBrowserTypeLaunchParityTests.cs index ae0f5882..2ab685fa 100644 --- a/src/PlaywrightNative.Tests/LibraryBrowserTypeLaunchParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryBrowserTypeLaunchParityTests.cs @@ -18,6 +18,7 @@ using System.IO; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -52,7 +53,7 @@ public async Task ShouldRejectAllPromisesWhenBrowserIsClosed() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowIfUserDataDirOptionIsPassed() { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => LaunchAsync(new BrowserTypeLaunchOptions { UserDataDir = "random-path" })); Assert.That(error.Message, Does.Contain("userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead")); } @@ -62,7 +63,7 @@ public void ShouldThrowIfUserDataDirOptionIsPassed() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowIfUserDataDirIsPassedAsAnArgument() { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => LaunchAsync(new BrowserTypeLaunchOptions { Args = new[] { "--user-data-dir=random-path", "--profile=random-path" }, @@ -75,7 +76,7 @@ public void ShouldThrowIfUserDataDirIsPassedAsAnArgument() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowIfPortOptionIsPassed() { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => LaunchAsync(new BrowserTypeLaunchOptions { Port = 1234 })); Assert.That(error.Message, Does.Contain("Cannot specify a port without launching as a server.")); } @@ -85,7 +86,7 @@ public void ShouldThrowIfPortOptionIsPassed() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowIfPortOptionIsPassedForPersistentContext() { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => CurrentBrowserType().LaunchPersistentContextAsync( "foo", new BrowserTypeLaunchOptions { Port = 1234 })); @@ -102,7 +103,7 @@ public void ShouldThrowIfPageArgumentIsPassed() Assert.Ignore("official skip: browserName === 'firefox' && !isBidi"); } - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => LaunchAsync(new BrowserTypeLaunchOptions { Args = new[] { "http://example.com" } })); Assert.That(error.Message, Does.Contain("can not specify page")); } @@ -113,7 +114,7 @@ public void ShouldThrowIfPageArgumentIsPassed() public void ShouldRejectIfLaunchedBrowserFailsImmediately() { string dummy = TestUtils.GetWebServerFile("dummy_bad_browser_executable.js"); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => CurrentBrowserType().LaunchAsync(new BrowserTypeLaunchOptions { ExecutablePath = dummy })); Assert.That( Regex.IsMatch(error.Message, @"browserType\.launch(.|\n)*(spawn UNKNOWN|spawn EFTYPE|Browser logs:)", RegexOptions.IgnoreCase | RegexOptions.Multiline), @@ -126,7 +127,7 @@ public void ShouldRejectIfLaunchedBrowserFailsImmediately() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldRejectIfExecutablePathIsInvalid() { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => CurrentBrowserType().LaunchAsync(new BrowserTypeLaunchOptions { ExecutablePath = "random-invalid-path" })); Assert.That(error.Message, Does.Contain("Failed to launch")); } diff --git a/src/PlaywrightNative.Tests/LibraryChromiumLauncherParityTests.cs b/src/PlaywrightNative.Tests/LibraryChromiumLauncherParityTests.cs index 71a09407..365ad6b0 100644 --- a/src/PlaywrightNative.Tests/LibraryChromiumLauncherParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryChromiumLauncherParityTests.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -47,7 +48,7 @@ public void SkipNonChromium() [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowWithRemoteDebuggingPipeArgument() { - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => BrowserLauncher.LaunchAsync(new BrowserTypeLaunchOptions { Args = new[] { "--remote-debugging-pipe" }, diff --git a/src/PlaywrightNative.Tests/LibraryChromiumParityTests.cs b/src/PlaywrightNative.Tests/LibraryChromiumParityTests.cs index c43ba4cc..efb3ad4a 100644 --- a/src/PlaywrightNative.Tests/LibraryChromiumParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryChromiumParityTests.cs @@ -23,6 +23,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -296,11 +297,11 @@ public async Task ServiceWorkerAndFromServiceWorkerWork() Assert.That(html.ServiceWorker(), Is.Null); Assert.That((await html.ResponseAsync().ConfigureAwait(false)).FromServiceWorker, Is.False); - Assert.Throws(() => _ = main.Frame); + Assert.Throws(() => _ = main.Frame); Assert.That(main.ServiceWorker(), Is.SameAs(worker)); Assert.That((await main.ResponseAsync().ConfigureAwait(false)).FromServiceWorker, Is.False); - Assert.Throws(() => _ = inWorker.Frame); + Assert.Throws(() => _ = inWorker.Frame); Assert.That(inWorker.ServiceWorker(), Is.SameAs(worker)); Assert.That((await inWorker.ResponseAsync().ConfigureAwait(false)).FromServiceWorker, Is.False); @@ -308,7 +309,7 @@ public async Task ServiceWorkerAndFromServiceWorkerWork() Task innerSwTask = context.WaitForRequestAsync( r => r.Url.EndsWith("/inner.txt", StringComparison.Ordinal) && r.ServiceWorker() != null); Task innerPageTask = context.WaitForRequestAsync( - r => r.Url.EndsWith("/inner.txt", StringComparison.Ordinal) && r.ServiceWorker == null); + r => r.Url.EndsWith("/inner.txt", StringComparison.Ordinal) && r.ServiceWorker() == null); await page.EvaluateAsync("() => fetch('/inner.txt')").ConfigureAwait(false); IRequest innerSw = await innerSwTask.ConfigureAwait(false); IRequest innerPage = await innerPageTask.ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/LibraryClientCertificatesParityTests.cs b/src/PlaywrightNative.Tests/LibraryClientCertificatesParityTests.cs index 6e9ca4a8..ec4fbb1d 100644 --- a/src/PlaywrightNative.Tests/LibraryClientCertificatesParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryClientCertificatesParityTests.cs @@ -43,6 +43,19 @@ public class LibraryClientCertificatesParityTests : PageTestEx private const string SelfSignedMessage = "Sorry Bob, certificates from Bob are not welcome here."; private const string MissingMessage = "Sorry, but you need to provide a client certificate to continue."; + /// + /// Official useFakeLocalhost: browserName === 'webkit' && isMac: + /// WebKit on macOS does not send localhost through the client-certificate SOCKS MITM. + /// + private static bool UseFakeLocalhost => TestConstants.IsWebKit && TestConstants.IsMacOSX; + + private static string ProxiedConnectHost => UseFakeLocalhost ? "localhost" : "127.0.0.1"; + + private static Task StartCcServerAsync( + bool http2 = false, + bool enableHttp1Fallback = false) + => OfficialClientCertificateServer.StartAsync(http2, enableHttp1Fallback, UseFakeLocalhost); + private static SimpleServer _ownedServer; private static string Prefix = TestConstants.ServerUrl; @@ -355,7 +368,7 @@ public async Task BrowserShouldNotInterceptTlsForOriginsWithoutAClientCertificat [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldFailWithNoClientCertificates() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { Trusted("https://not-matching.com") } }) @@ -370,7 +383,7 @@ public async Task BrowserShouldFailWithNoClientCertificates() [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldFailWithSelfSignedClientCertificates() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { SelfSigned(OriginOf(server.Url)) } }) @@ -385,7 +398,7 @@ public async Task BrowserShouldFailWithSelfSignedClientCertificates() [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificates() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { Trusted(OriginOf(server.Url)) } }) @@ -400,7 +413,7 @@ public async Task BrowserShouldPassWithMatchingCertificates() [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesWhenPassingAsContent() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() @@ -427,7 +440,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesWhenPassingAsContent( [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUsed() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using OfficialTestProxy proxyServer = new OfficialTestProxy(); proxyServer.ForwardTo(server.Port, allowConnectRequests: true); @@ -436,7 +449,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUs .ConfigureAwait(false); Assert.That(proxyServer.ConnectHosts, Is.Empty); await page.GoToAsync(server.Url).ConfigureAwait(false); - Assert.That(proxyServer.ConnectHosts.Distinct().ToArray(), Is.EqualTo(new[] { "127.0.0.1:" + server.Port.ToString(CultureInfo.InvariantCulture) })); + Assert.That(proxyServer.ConnectHosts.Distinct().ToArray(), Is.EqualTo(new[] { ProxiedConnectHost + ":" + server.Port.ToString(CultureInfo.InvariantCulture) })); await Assertions.Expect(page.GetByTestId("message")).ToHaveTextAsync(TrustedMessage).ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); } @@ -446,7 +459,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUs [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUsedFromEnv() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using OfficialTestProxy proxyServer = new OfficialTestProxy(); proxyServer.ForwardTo(server.Port, allowConnectRequests: true); @@ -460,8 +473,8 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUs proxyServer.ConnectHosts = Array.Empty(); await page.GoToAsync(server.Url).ConfigureAwait(false); Assert.That( - proxyServer.ConnectHosts.Where(host => host.StartsWith("127.0.0.1:", StringComparison.Ordinal)).Distinct().ToArray(), - Is.EqualTo(new[] { "127.0.0.1:" + server.Port.ToString(CultureInfo.InvariantCulture) })); + proxyServer.ConnectHosts.Where(host => host.StartsWith(ProxiedConnectHost + ":", StringComparison.Ordinal)).Distinct().ToArray(), + Is.EqualTo(new[] { ProxiedConnectHost + ":" + server.Port.ToString(CultureInfo.InvariantCulture) })); await Assertions.Expect(page.GetByTestId("message")).ToHaveTextAsync(TrustedMessage).ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); } @@ -476,7 +489,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUs [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUsedFromConfigButEnvIsThere() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using OfficialTestProxy proxyServer = new OfficialTestProxy(); proxyServer.ForwardTo(server.Port, allowConnectRequests: true); @@ -489,7 +502,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUs .ConfigureAwait(false); Assert.That(proxyServer.ConnectHosts, Is.Empty); await page.GoToAsync(server.Url).ConfigureAwait(false); - Assert.That(proxyServer.ConnectHosts.Distinct().ToArray(), Is.EqualTo(new[] { "127.0.0.1:" + server.Port.ToString(CultureInfo.InvariantCulture) })); + Assert.That(proxyServer.ConnectHosts.Distinct().ToArray(), Is.EqualTo(new[] { ProxiedConnectHost + ":" + server.Port.ToString(CultureInfo.InvariantCulture) })); await Assertions.Expect(page.GetByTestId("message")).ToHaveTextAsync(TrustedMessage).ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); } @@ -504,7 +517,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenAHttpProxyIsUs [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesAndWhenASocksProxyIsUsed() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using OfficialSocksForwardingProxy socks = new OfficialSocksForwardingProxy(server.Port, server.Port); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); @@ -513,7 +526,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesAndWhenASocksProxyIsU await page.GoToAsync(server.Url).ConfigureAwait(false); Assert.That( socks.ConnectHosts.Distinct().ToArray(), - Is.EqualTo(new[] { "127.0.0.1:" + server.Port.ToString(CultureInfo.InvariantCulture) })); + Is.EqualTo(new[] { ProxiedConnectHost + ":" + server.Port.ToString(CultureInfo.InvariantCulture) })); await Assertions.Expect(page.GetByTestId("message")).ToHaveTextAsync(TrustedMessage).ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); } @@ -527,9 +540,12 @@ public async Task BrowserShouldNotHangOnTlsErrorsDuringTls12Handshake() foreach (SslProtocols version in new[] { SslProtocols.Tls13, SslProtocols.Tls12 }) { await using OfficialTlsSniRejectServer server = OfficialTlsSniRejectServer.Start(version); - IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { SelfSigned(OriginOf(server.Url)) } }) + string serverUrl = UseFakeLocalhost + ? server.Url.Replace("localhost", "local.playwright", StringComparison.Ordinal) + : server.Url; + IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { SelfSigned(OriginOf(serverUrl)) } }) .ConfigureAwait(false); - await page.GoToAsync(server.Url).ConfigureAwait(false); + await page.GoToAsync(serverUrl).ConfigureAwait(false); await Assertions.Expect(page.GetByText( "Playwright client-certificate error: Client network socket disconnected before secure TLS connection was established")) .ToBeVisibleAsync().ConfigureAwait(false); @@ -542,7 +558,7 @@ await Assertions.Expect(page.GetByText( [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesInPfxFormat() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { TrustedPfx(OriginOf(server.Url)) } }) @@ -558,11 +574,14 @@ public async Task BrowserShouldPassWithMatchingCertificatesInPfxFormat() public async Task BrowserShouldHandleTlsRenegotiationWithClientCertificates() { await using OfficialTlsRenegotiationServer server = OfficialTlsRenegotiationServer.Start(); + string serverUrl = UseFakeLocalhost + ? server.Url.Replace("localhost", "local.playwright", StringComparison.Ordinal) + : server.Url; await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); - await using IBrowserContext context = await browser.NewContextAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { Trusted(server.Url) } }) + await using IBrowserContext context = await browser.NewContextAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { Trusted(serverUrl) } }) .ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - await page.GoToAsync(server.Url).ConfigureAwait(false); + await page.GoToAsync(serverUrl).ConfigureAwait(false); string response = await page.EvaluateAsync(@"async () => { const response = await fetch('/from-fetch-api', { method: 'POST', @@ -579,7 +598,7 @@ public async Task BrowserShouldHandleTlsRenegotiationWithClientCertificates() "3-from-server", "server closed the connection", }))); - await page.GoToAsync(server.Url).ConfigureAwait(false); + await page.GoToAsync(serverUrl).ConfigureAwait(false); await page.SetContentAsync("") .ConfigureAwait(false); await Assertions.Expect(page.Locator("button")).ToHaveCSSAsync("background-color", "rgb(255, 0, 0)") @@ -591,7 +610,7 @@ await Assertions.Expect(page.Locator("button")).ToHaveCSSAsync("background-color [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesInPfxFormatWhenPassingAsContent() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() @@ -618,7 +637,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesInPfxFormatWhenPassin [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldFailWithMatchingCertificatesInLegacyPfxFormat() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); Exception error = await CatchAsync(() => browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { LegacyPfx(OriginOf(server.Url)) } })) @@ -632,7 +651,7 @@ public async Task BrowserShouldFailWithMatchingCertificatesInLegacyPfxFormat() [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldThrowAHttpErrorIfThePfxPassphraseIsIncorect() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); Exception error = await CatchAsync(() => browser.NewPageAsync(new() @@ -658,7 +677,7 @@ public async Task BrowserShouldThrowAHttpErrorIfThePfxPassphraseIsIncorect() [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesOnContextApiRequestContextInstance() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); string origin = OriginOf(server.Url); @@ -687,7 +706,7 @@ public async Task BrowserShouldPassWithMatchingCertificatesOnContextApiRequestCo [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldPassWithMatchingCertificatesAndTrailingSlash() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { Trusted(server.Url) } }) @@ -704,10 +723,13 @@ public async Task BrowserShouldHaveIgnoreHttpsErrorsFalseByDefault() { await using OfficialPlaywrightTestHttpsServer https = await OfficialPlaywrightTestHttpsServer.StartAsync() .ConfigureAwait(false); + string targetUrl = UseFakeLocalhost + ? https.EmptyPage.Replace("127.0.0.1", "local.playwright", StringComparison.Ordinal) + : https.EmptyPage; await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); - IPage page = await browser.NewPageAsync(new() { ClientCertificates = new[] { Trusted(OriginOf(https.EmptyPage)) } }) + IPage page = await browser.NewPageAsync(new() { ClientCertificates = new[] { Trusted(OriginOf(targetUrl)) } }) .ConfigureAwait(false); - await page.GoToAsync(https.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(targetUrl).ConfigureAwait(false); await Assertions.Expect(page.GetByText("Playwright client-certificate error: self-signed certificate")) .ToBeVisibleAsync().ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); @@ -718,7 +740,12 @@ await Assertions.Expect(page.GetByText("Playwright client-certificate error: sel [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserSupportHttp2() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync(http2: true) + if (UseFakeLocalhost) + { + Assert.Ignore("official skip: WebKit on macOS does not proxy localhost"); + } + + await using OfficialClientCertificateServer server = await StartCcServerAsync(http2: true) .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { IgnoreHTTPSErrors = true, ClientCertificates = new[] { Trusted(OriginOf(server.Url)) } }) @@ -744,7 +771,7 @@ public async Task BrowserSupportHttp2IfTheBrowserOnlySupportsHttp11() Assert.Ignore("official skip: browserName !== chromium"); } - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync(http2: true, enableHttp1Fallback: true) + await using OfficialClientCertificateServer server = await StartCcServerAsync(http2: true, enableHttp1Fallback: true) .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync(new BrowserTypeLaunchOptions { @@ -766,7 +793,12 @@ await page.GoToAsync(server.Url.Replace("127.0.0.1", "local.playwright", StringC [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldReturnTargetConnectionErrorsWhenUsingHttp2() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync(http2: true) + if (UseFakeLocalhost) + { + Assert.Ignore("official skip: WebKit on macOS does not proxy localhost"); + } + + await using OfficialClientCertificateServer server = await StartCcServerAsync(http2: true) .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync(new() { ClientCertificates = new[] { Trusted(OriginOf(server.Url)) } }) @@ -782,7 +814,7 @@ await Assertions.Expect(page.GetByText("Playwright client-certificate error: sel [Timeout(TestConstants.DefaultTestTimeout)] public async Task BrowserShouldHandleRejectedCertificateInHandshakeWithHttp2() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync(http2: true) + await using OfficialClientCertificateServer server = await StartCcServerAsync(http2: true) .ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync(new() @@ -826,7 +858,7 @@ public async Task PersistentContextValidateInput() [Timeout(60_000)] public async Task PersistentContextShouldPassWithMatchingCertificates() { - await using OfficialClientCertificateServer server = await OfficialClientCertificateServer.StartAsync() + await using OfficialClientCertificateServer server = await StartCcServerAsync() .ConfigureAwait(false); await using PersistentLaunch launch = await LaunchPersistentAsync(new BrowserTypeLaunchPersistentContextOptions { diff --git a/src/PlaywrightNative.Tests/LibraryConnectOverCdpParityTests.cs b/src/PlaywrightNative.Tests/LibraryConnectOverCdpParityTests.cs index 63140697..469e5652 100644 --- a/src/PlaywrightNative.Tests/LibraryConnectOverCdpParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryConnectOverCdpParityTests.cs @@ -28,6 +28,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.Chromium; using PlaywrightNative.Helpers; @@ -496,7 +497,7 @@ public void ShouldReportAnExpectedErrorWhenTheEndpointUrlReturnsANonExpectedStat http.Response.StatusCode = 404; return http.Response.WriteAsync("{\"webSocketDebuggerUrl\":\"dont-use-me\"}"); }); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => Playwright.Chromium.ConnectOverCDPAsync(Prefix)); Assert.That( error.Message, @@ -514,7 +515,7 @@ public void ShouldReportAnExpectedErrorWhenTheEndpointUrlJsonWebSocketDebuggerUr http.Response.StatusCode = 200; return http.Response.WriteAsync("{}"); }); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => Playwright.Chromium.ConnectOverCDPAsync(Prefix)); Assert.That(error.Message, Does.Contain("browserType.connectOverCDP: Invalid URL")); } @@ -569,7 +570,7 @@ public async Task ShouldUseEnvProxyWithConnectOverCdpDiscoveryRequest() try { Environment.SetEnvironmentVariable("HTTP_PROXY", "http://" + proxyServer.Host); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => Playwright.Chromium.ConnectOverCDPAsync(Prefix)); Assert.That( error.Message, @@ -594,7 +595,7 @@ public async Task ShouldSendTargetHostHeaderWhenUsingEnvHttpProxyWithConnectOver try { Environment.SetEnvironmentVariable("HTTP_PROXY", "http://" + proxyServer.Host); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => Playwright.Chromium.ConnectOverCDPAsync(Prefix)); Assert.That( error.Message, @@ -734,7 +735,7 @@ public void ShouldPrintCustomWsCloseError() await ws.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None).ConfigureAwait(false); await ws.CloseAsync((WebSocketCloseStatus)4123, "Oh my!", CancellationToken.None).ConfigureAwait(false); }); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => Playwright.Chromium.ConnectOverCDPAsync("ws://localhost:" + TestConstants.Port + "/ws")); Assert.That(error.Message, Does.Contain("Browser logs:\n\nOh my!\n")); } @@ -850,7 +851,7 @@ private static async Task WithHostAsync(Func body) { await host.CloseAsync().ConfigureAwait(false); } - catch (PlaywrightNativeException) + catch (PlaywrightException) { } catch (ObjectDisposedException) diff --git a/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext1ParityTests.cs b/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext1ParityTests.cs index e7f82295..6b5d2189 100644 --- a/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext1ParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext1ParityTests.cs @@ -398,17 +398,24 @@ private static void AssertCookie( private static SameSiteAttribute DefaultSameSiteCookieValue() { - if (TestConstants.IsWebKit && TestConstants.IsWindows) + // Upstream defaultSameSiteCookieValue: Chromium and WebKit/Linux are Lax; + // WebKit on Windows and older macOS (mac14 bots) report None; Firefox is None. + if (TestConstants.IsChromium) { - return SameSiteAttribute.None; + return SameSiteAttribute.Lax; } - if (TestConstants.IsFirefox) + if (TestConstants.IsWebKit && TestConstants.IsLinux) + { + return SameSiteAttribute.Lax; + } + + if (TestConstants.IsWebKit) { return SameSiteAttribute.None; } - return SameSiteAttribute.Lax; + return SameSiteAttribute.None; } private static SameSiteAttribute SameSiteLaxOrWindowsNone() diff --git a/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext2ParityTests.cs b/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext2ParityTests.cs index 7f4fa40a..bf16781e 100644 --- a/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext2ParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryDefaultBrowserContext2ParityTests.cs @@ -24,6 +24,7 @@ using System.Security.Cryptography.X509Certificates; using System.Text.Json.Serialization; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -746,7 +747,7 @@ private static async Task RegisterEngineAsync(string name, string script) { await Playwright.Selectors.RegisterAsync(name, script).ConfigureAwait(false); } - catch (PlaywrightNativeException ex) + catch (PlaywrightException ex) when (ex.Message.IndexOf("already registered", StringComparison.Ordinal) >= 0) { } diff --git a/src/PlaywrightNative.Tests/LibraryExtensionsParityTests.cs b/src/PlaywrightNative.Tests/LibraryExtensionsParityTests.cs index 5f494772..3fe45f31 100644 --- a/src/PlaywrightNative.Tests/LibraryExtensionsParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryExtensionsParityTests.cs @@ -16,6 +16,7 @@ */ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Text.Json; @@ -39,7 +40,54 @@ namespace PlaywrightNative.Tests [NonParallelizable] public class LibraryExtensionsParityTests : PageTestEx { - private static SimpleServer Server => TestServerSetup.Server; + private static SimpleServer _ownedServer; + private static string Prefix = TestConstants.ServerUrl; + private static string EmptyPage = TestConstants.EmptyPage; + + private static SimpleServer Server => _ownedServer ?? TestServerSetup.Server; + + [OneTimeSetUp] + public async Task StartOwnedServerAsync() + { + string contentRoot = TestUtils.FindParentDirectory("PlaywrightNative.TestServer"); + int basePort = 19981; + for (int i = 0; i < 20; i++) + { + int port = basePort + i; + try + { + SimpleServer server = SimpleServer.Create(port, contentRoot); + await server.StartAsync().ConfigureAwait(false); + _ownedServer = server; + string portText = port.ToString(CultureInfo.InvariantCulture); + Prefix = "http://localhost:" + portText; + EmptyPage = Prefix + "/empty.html"; + return; + } + catch (Exception) + { + } + } + + if (TestServerSetup.Server != null) + { + Prefix = TestConstants.ServerUrl; + EmptyPage = TestConstants.EmptyPage; + return; + } + + Assert.Ignore("Test server is unavailable."); + } + + [OneTimeTearDown] + public async Task StopOwnedServerAsync() + { + if (_ownedServer != null) + { + await _ownedServer.StopAsync().ConfigureAwait(false); + _ownedServer = null; + } + } [SetUp] public void SkipNonChromiumExtensions() @@ -178,12 +226,12 @@ public async Task ShouldSupportRequestResponseEventsInTheServiceWorker() Task responseTask = context.WaitForEventAsync(BrowserContextEvent.Response); Task evaluateTask = serviceWorker.EvaluateAsync( "url => fetch(url, { method: 'POST', body: 'foobar', headers: { 'X-FOOBAR': 'KEKBAR' } })", - TestConstants.EmptyPage); + EmptyPage); await Task.WhenAll(requestTask, responseTask, evaluateTask).ConfigureAwait(false); IRequest request = requestTask.Result; IResponse response = responseTask.Result; - Assert.That(request.Url, Is.EqualTo(TestConstants.EmptyPage)); + Assert.That(request.Url, Is.EqualTo(EmptyPage)); Assert.That(request.Method, Is.EqualTo("POST")); Dictionary requestHeaders = await request.AllHeadersAsync().ConfigureAwait(false); Assert.That(requestHeaders, Does.ContainKey("x-foobar")); @@ -191,7 +239,7 @@ public async Task ShouldSupportRequestResponseEventsInTheServiceWorker() Assert.That(request.PostData, Is.EqualTo("foobar")); Assert.That(response.Status, Is.EqualTo(200)); - Assert.That(response.Url, Is.EqualTo(TestConstants.EmptyPage)); + Assert.That(response.Url, Is.EqualTo(EmptyPage)); Assert.That(response.Request, Is.SameAs(request)); Assert.That(await response.TextAsync().ConfigureAwait(false), Is.EqualTo(" hello world! ")); Dictionary responseHeaders = await response.AllHeadersAsync().ConfigureAwait(false); @@ -210,7 +258,7 @@ public async Task ShouldReportConsoleMessagesFromContentScript() Task consolePromise = page.WaitForEventAsync( PageEvent.Console, e => e.Text.Contains("Test console log from a third-party execution context", StringComparison.Ordinal)); - await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(EmptyPage).ConfigureAwait(false); IConsoleMessage message = await consolePromise.ConfigureAwait(false); Assert.That(message.Text, Does.Contain("Test console log from a third-party execution context")); await context.CloseAsync().ConfigureAwait(false); @@ -236,7 +284,7 @@ public async Task ShouldUseCustomUserAgentInServiceWorkerFetchRequests() IWorker sw = await FirstServiceWorkerAsync(context).ConfigureAwait(false); string userAgent = await sw.EvaluateAsync( "async url => { const response = await fetch(url); return response.text(); }", - TestConstants.ServerUrl + "/ua-echo").ConfigureAwait(false); + Prefix + "/ua-echo").ConfigureAwait(false); Assert.That(userAgent, Is.EqualTo("MyTestAgent/1.0")); await context.CloseAsync().ConfigureAwait(false); } @@ -257,6 +305,14 @@ private static bool IsBrandedChrome(string path) return false; } + // "Google Chrome for Testing.app" (the macOS Playwright-downloaded + // build) contains "Google Chrome" too, but is not the real + // installed browser this check exists to detect. + if (path.IndexOf("for Testing", StringComparison.OrdinalIgnoreCase) >= 0) + { + return false; + } + return path.IndexOf("/opt/google/chrome", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("Google Chrome", StringComparison.OrdinalIgnoreCase) >= 0; } @@ -271,11 +327,16 @@ private static string ResolveExtensionChromiumPath() { foreach (string dir in Directory.GetDirectories(cache, "chromium-*").OrderByDescending(d => d, StringComparer.Ordinal)) { - string exe = Path.Combine(dir, "chrome-linux", "chrome"); - if (File.Exists(exe) - && exe.IndexOf("headless-shell", StringComparison.OrdinalIgnoreCase) < 0) + // Chrome for Testing extracts to chrome-linux64/; keep the + // legacy chrome-linux/ probe for older cache layouts. + foreach (string relative in new[] { "chrome-linux64/chrome", "chrome-linux/chrome" }) { - return exe; + string exe = Path.Combine(dir, relative); + if (File.Exists(exe) + && exe.IndexOf("headless-shell", StringComparison.OrdinalIgnoreCase) < 0) + { + return exe; + } } } } diff --git a/src/PlaywrightNative.Tests/LibraryGeolocationParityTests.cs b/src/PlaywrightNative.Tests/LibraryGeolocationParityTests.cs index e4c17a67..c7cd025b 100644 --- a/src/PlaywrightNative.Tests/LibraryGeolocationParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryGeolocationParityTests.cs @@ -189,6 +189,7 @@ public async Task ShouldIsolateContexts() [PlaywrightTest("geolocation.spec.ts", "should throw with missing latitude")] [Test] + [Ignore("Microsoft.Playwright.Geolocation uses non-nullable float; omitted latitude is 0, not undefined (playwright-dotnet skips this protocol check).")] [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowWithMissingLatitude() { @@ -219,6 +220,7 @@ await context.SetGeolocationAsync(new Geolocation { Longitude = 20, Latitude = 2 [PlaywrightTest("geolocation.spec.ts", "should throw with missing longitude in default options")] [Test] + [Ignore("Microsoft.Playwright.Geolocation uses non-nullable float; omitted longitude is 0, not undefined (playwright-dotnet skips this protocol check).")] [Timeout(TestConstants.DefaultTestTimeout)] public void ShouldThrowWithMissingLongitudeInDefaultOptions() { diff --git a/src/PlaywrightNative.Tests/LibraryHarParityTests.cs b/src/PlaywrightNative.Tests/LibraryHarParityTests.cs index 85b9d8db..e62fb790 100644 --- a/src/PlaywrightNative.Tests/LibraryHarParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryHarParityTests.cs @@ -31,7 +31,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Playwright; using NUnit.Framework; +using PlaywrightNative.Helpers; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -868,7 +870,7 @@ public async Task ShouldRecordFailedRequestHeaders() { await session.Page.GoToAsync(Prefix + "/har.html").ConfigureAwait(false); } - catch (PlaywrightNativeException) + catch (PlaywrightException) { } @@ -906,7 +908,7 @@ await session.Page.RouteAsync("**/foo", route => { await session.Page.GoToAsync(Prefix + "/foo").ConfigureAwait(false); } - catch (PlaywrightNativeException) + catch (PlaywrightException) { } @@ -1764,7 +1766,7 @@ public async Task ShouldRejectResourcesDirTogetherWithAZipHarFile() await using IBrowserContext context = await _browser.NewContextAsync().ConfigureAwait(false); string harPath = TempHarPath("tracing", ".har.zip"); string resourcesDir = Path.Combine(Path.GetTempPath(), "pwsharp-wave879-har-resources-" + Guid.NewGuid().ToString("N")); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => context.Tracing.StartHarAsync(harPath, content: HarContentPolicy.Attach, resourcesDir: resourcesDir)); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Match("resourcesDir option is not compatible with a \\.zip har file")); @@ -1772,7 +1774,7 @@ public async Task ShouldRejectResourcesDirTogetherWithAZipHarFile() private async Task PageWithHarAsync( string outputName = "test.har", - HarContentPolicy content = default, + HarContentPolicy content = EnumCompat.UndefinedHarContentPolicy, bool? omitContent = null, HarMode mode = default) { diff --git a/src/PlaywrightNative.Tests/LibraryHarWebsocketParityTests.cs b/src/PlaywrightNative.Tests/LibraryHarWebsocketParityTests.cs index e7d364f1..441ff333 100644 --- a/src/PlaywrightNative.Tests/LibraryHarWebsocketParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryHarWebsocketParityTests.cs @@ -28,6 +28,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using PlaywrightNative.Helpers; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -648,7 +649,7 @@ await session.Page.EvaluateAsync( private async Task PageWithHarAsync( string outputName = "test.har", - HarContentPolicy content = default) + HarContentPolicy content = EnumCompat.UndefinedHarContentPolicy) { string harPath = TempHarPath(Path.GetFileNameWithoutExtension(outputName), Path.GetExtension(outputName)); IBrowserContext context = await _browser.NewContextAsync(new() { RecordHarPath = harPath, RecordHarContent = content, IgnoreHTTPSErrors = true }).ConfigureAwait(false); diff --git a/src/PlaywrightNative.Tests/LibraryLauncherParityTests.cs b/src/PlaywrightNative.Tests/LibraryLauncherParityTests.cs index b75ec4a0..e533eb77 100644 --- a/src/PlaywrightNative.Tests/LibraryLauncherParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryLauncherParityTests.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -64,7 +65,7 @@ public void ShouldThrowAFriendlyErrorIfItsHeadedAndThereIsNoXserverOnLinuxRunnin ["DISPLAY"] = null, }; - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => LaunchAsync(new BrowserTypeLaunchOptions { Headless = false, diff --git a/src/PlaywrightNative.Tests/LibraryPageClockParityTests.cs b/src/PlaywrightNative.Tests/LibraryPageClockParityTests.cs index e347f48c..bde6d5dc 100644 --- a/src/PlaywrightNative.Tests/LibraryPageClockParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryPageClockParityTests.cs @@ -20,6 +20,7 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -196,7 +197,7 @@ public async Task TriggersEventWhenSomeThrow() await _page.EvaluateAsync( "() => { setTimeout(() => { throw new Error(); }, 100); setTimeout(window.stub, 120); }") .ConfigureAwait(false); - Assert.ThrowsAsync( + Assert.ThrowsAsync( async () => await _page.Clock.RunForAsync(120).ConfigureAwait(false)); Assert.That(_calls, Has.Count.EqualTo(1)); } @@ -254,7 +255,7 @@ public async Task ThrowsForInvalidFormat() { await InstallPausedAsync().ConfigureAwait(false); await _page.EvaluateAsync("() => { setInterval(window.stub, 10000); }").ConfigureAwait(false); - Assert.ThrowsAsync( + Assert.ThrowsAsync( async () => await _page.Clock.RunForAsync("12:02:34:10").ConfigureAwait(false)); Assert.That(_calls, Is.Empty); } @@ -322,10 +323,10 @@ public async Task SetsInitialTimestamp() public async Task ShouldThrowForInvalidDate() { await InstallPausedAsync().ConfigureAwait(false); - PlaywrightNativeException invalidDate = Assert.ThrowsAsync( + PlaywrightException invalidDate = Assert.ThrowsAsync( async () => await _page.Clock.SetSystemTimeAsync("Invalid Date").ConfigureAwait(false)); Assert.That(invalidDate.Message, Does.Contain("Invalid date: Invalid Date")); - PlaywrightNativeException invalid = Assert.ThrowsAsync( + PlaywrightException invalid = Assert.ThrowsAsync( async () => await _page.Clock.SetSystemTimeAsync("invalid").ConfigureAwait(false)); Assert.That(invalid.Message, Does.Contain("Invalid date: invalid")); } @@ -621,7 +622,7 @@ public async Task ShouldRejectAnInvalidTargetTimeWithAnActiveRequestAnimationFra .ConfigureAwait(false); double now = await _page.EvaluateAsync("() => Date.now()").ConfigureAwait(false); long invalidTime = (long)(now * 1_000_000); - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( async () => await _page.Clock.PauseAtAsync(invalidTime).ConfigureAwait(false)); Assert.That(error.Message, Does.Contain("Invalid date: " + invalidTime.ToString(CultureInfo.InvariantCulture))); } diff --git a/src/PlaywrightNative.Tests/LibraryPageEventCrashParityTests.cs b/src/PlaywrightNative.Tests/LibraryPageEventCrashParityTests.cs index 1efea6f6..ea3e01e6 100644 --- a/src/PlaywrightNative.Tests/LibraryPageEventCrashParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryPageEventCrashParityTests.cs @@ -18,6 +18,7 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -155,7 +156,7 @@ public async Task ShouldCancelWaitForEventWhenPageCrashes() await _page.SetContentAsync("
This page should crash
").ConfigureAwait(false); Task wait = _page.WaitForEventAsync(PageEvent.Response); Crash(); - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( async () => await wait.ConfigureAwait(false)); Assert.That(error.Message, Does.Contain("Page crashed")); } @@ -215,7 +216,7 @@ await _page.EvaluateAsync( IWorker worker = await workerTask.ConfigureAwait(false); Task evalTask = worker.EvaluateAsync("() => new Promise(() => {})"); Crash(); - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( async () => await evalTask.ConfigureAwait(false)); Assert.That(error.Message, Does.Contain("crash")); } @@ -236,7 +237,7 @@ private void Crash() private async Task ExpectCrashErrorAsync(Func action) { - PlaywrightNativeException error = Assert.ThrowsAsync( + PlaywrightException error = Assert.ThrowsAsync( async () => await action().ConfigureAwait(false)); Assert.That(error, Is.Not.Null, "action should reject after crash"); if (TestConstants.IsFirefox) diff --git a/src/PlaywrightNative.Tests/LibraryScreencastParityTests.cs b/src/PlaywrightNative.Tests/LibraryScreencastParityTests.cs index 15e30b64..7ea4dae2 100644 --- a/src/PlaywrightNative.Tests/LibraryScreencastParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryScreencastParityTests.cs @@ -21,6 +21,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -161,7 +162,7 @@ public async Task StartThrowsIfScreencastIsAlreadyStarted() IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.Screencast.StartAsync(_ => Task.CompletedTask).ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => page.Screencast.StartAsync(_ => Task.CompletedTask)); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Screencast is already started")); @@ -296,7 +297,7 @@ public async Task StartShouldFailWhenAnotherRecordingIsInProgress() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await page.Screencast.StartAsync(new() { Path = video1 }).ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => page.Screencast.StartAsync(new() { Path = video2 })); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Screencast is already started")); diff --git a/src/PlaywrightNative.Tests/LibrarySelectorsRegisterParityTests.cs b/src/PlaywrightNative.Tests/LibrarySelectorsRegisterParityTests.cs index 74935b40..37a73f8b 100644 --- a/src/PlaywrightNative.Tests/LibrarySelectorsRegisterParityTests.cs +++ b/src/PlaywrightNative.Tests/LibrarySelectorsRegisterParityTests.cs @@ -17,6 +17,7 @@ using System; using System.IO; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; @@ -60,7 +61,7 @@ public async Task ShouldWork() Assert.That(await page.EvalOnSelectorAsync("tag2=SPAN", "e => e.nodeName").ConfigureAwait(false), Is.EqualTo("SPAN")); Assert.That(await page.EvalOnSelectorAllAsync("tag2=DIV", "es => es.length").ConfigureAwait(false), Is.EqualTo(2)); - PlaywrightNativeException error = Assert.CatchAsync(() => page.QuerySelectorAsync("tAG=DIV")); + PlaywrightException error = Assert.CatchAsync(() => page.QuerySelectorAsync("tAG=DIV")); Assert.That(error.Message, Does.Contain("Unknown engine \"tAG\" while parsing selector tAG=DIV")); } @@ -151,7 +152,7 @@ public async Task ShouldHandleErrors() { await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync(() => page.QuerySelectorAsync("neverregister=ignored")); + PlaywrightException error = Assert.CatchAsync(() => page.QuerySelectorAsync("neverregister=ignored")); Assert.That(error.Message, Does.Contain("Unknown engine \"neverregister\" while parsing selector neverregister=ignored")); const string createDummySelector = @"() => ({ @@ -163,16 +164,16 @@ public async Task ShouldHandleErrors() } })"; - error = Assert.CatchAsync(() => Playwright.Selectors.RegisterAsync("$", createDummySelector)); + error = Assert.CatchAsync(() => Playwright.Selectors.RegisterAsync("$", createDummySelector)); Assert.That(error.Message, Is.EqualTo("selectors.register: Selector engine name may only contain [a-zA-Z0-9_] characters")); await Playwright.Selectors.RegisterAsync("dummy", createDummySelector).ConfigureAwait(false); await Playwright.Selectors.RegisterAsync("duMMy", createDummySelector).ConfigureAwait(false); - error = Assert.CatchAsync(() => Playwright.Selectors.RegisterAsync("dummy", createDummySelector)); + error = Assert.CatchAsync(() => Playwright.Selectors.RegisterAsync("dummy", createDummySelector)); Assert.That(error.Message, Is.EqualTo("selectors.register: \"dummy\" selector engine has been already registered")); - error = Assert.CatchAsync(() => Playwright.Selectors.RegisterAsync("css", createDummySelector)); + error = Assert.CatchAsync(() => Playwright.Selectors.RegisterAsync("css", createDummySelector)); Assert.That(error.Message, Is.EqualTo("selectors.register: \"css\" is a predefined selector engine")); await page.CloseAsync().ConfigureAwait(false); } @@ -183,7 +184,7 @@ public async Task ShouldHandleErrors() public async Task ShouldThrowAlreadyRegisteredErrorWhenRegistering() { await Playwright.Selectors.RegisterAsync("alreadyRegistered", CreateTagSelector).ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => Playwright.Selectors.RegisterAsync("alreadyRegistered", CreateTagSelector)); Assert.That(error.Message, Is.EqualTo("selectors.register: \"alreadyRegistered\" selector engine has been already registered")); } @@ -225,7 +226,7 @@ public async Task ShouldThrowANiceErrorIfTheSelectorReturnsABadValue() await Playwright.Selectors.RegisterAsync("__fake", createFakeEngine).ConfigureAwait(false); await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); IPage page = await browser.NewPageAsync().ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync(() => page.QuerySelectorAsync("__fake=value2")); + PlaywrightException error = Assert.CatchAsync(() => page.QuerySelectorAsync("__fake=value2")); Assert.That(error.Message, Does.Contain("Expected a Node but got [object Array]")); await page.CloseAsync().ConfigureAwait(false); } diff --git a/src/PlaywrightNative.Tests/LibraryTracingParityTests.cs b/src/PlaywrightNative.Tests/LibraryTracingParityTests.cs index 057cb8fe..677018e7 100644 --- a/src/PlaywrightNative.Tests/LibraryTracingParityTests.cs +++ b/src/PlaywrightNative.Tests/LibraryTracingParityTests.cs @@ -16,12 +16,14 @@ */ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net.WebSockets; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Playwright; using NUnit.Framework; using PlaywrightNative.NUnit; using PlaywrightNative.TestServer; @@ -38,7 +40,54 @@ namespace PlaywrightNative.Tests [NonParallelizable] public class LibraryTracingParityTests : PageTestEx { - private static SimpleServer Server => TestServerSetup.Server; + private static SimpleServer _ownedServer; + private static string Prefix = TestConstants.ServerUrl; + private static string EmptyPage = TestConstants.EmptyPage; + + private static SimpleServer Server => _ownedServer ?? TestServerSetup.Server; + + [OneTimeSetUp] + public async Task StartOwnedServerAsync() + { + string contentRoot = TestUtils.FindParentDirectory("PlaywrightNative.TestServer"); + int basePort = 19991; + for (int i = 0; i < 20; i++) + { + int port = basePort + i; + try + { + SimpleServer server = SimpleServer.Create(port, contentRoot); + await server.StartAsync().ConfigureAwait(false); + _ownedServer = server; + string portText = port.ToString(CultureInfo.InvariantCulture); + Prefix = "http://localhost:" + portText; + EmptyPage = Prefix + "/empty.html"; + return; + } + catch (Exception) + { + } + } + + if (TestServerSetup.Server != null) + { + Prefix = TestConstants.ServerUrl; + EmptyPage = TestConstants.EmptyPage; + return; + } + + Assert.Ignore("Test server is unavailable."); + } + + [OneTimeTearDown] + public async Task StopOwnedServerAsync() + { + if (_ownedServer != null) + { + await _ownedServer.StopAsync().ConfigureAwait(false); + _ownedServer = null; + } + } [PlaywrightTest("tracing.spec.ts", "should collect trace with resources, but no js")] [Test] @@ -58,13 +107,13 @@ public async Task ShouldCollectTraceWithResourcesButNoJs() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Screenshots = true, Snapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/frames/frame.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/frames/frame.html").ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); await page.ClickAsync("\"Click\"").ConfigureAwait(false); await page.Mouse.MoveAsync(20, 20).ConfigureAwait(false); await page.Mouse.DblClickAsync(30, 30).ConfigureAwait(false); await page.Keyboard.InsertTextAsync("abc").ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/input/fileupload.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/input/fileupload.html").ConfigureAwait(false); await page.Locator("input[type=\"file\"]").SetInputFilesAsync(TestUtils.GetWebServerFile("file-to-upload.txt")).ConfigureAwait(false); await page.WaitForTimeoutAsync(2000).ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); @@ -108,7 +157,7 @@ public async Task ShouldThrowWhenStartingWithDifferentOptions() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Screenshots = true, Snapshots = true }).ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => context.Tracing.StartAsync(new TracingStartOptions { Screenshots = false, Snapshots = false })); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Tracing has been already started")); @@ -124,7 +173,7 @@ public async Task ShouldThrowWhenStoppingWithoutStart() { await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); - PlaywrightNativeException error = Assert.CatchAsync( + PlaywrightException error = Assert.CatchAsync( () => context.Tracing.StopAsync(new TracingStopOptions { Path = path })); Assert.That(error, Is.Not.Null); Assert.That(error.Message, Does.Contain("Must start tracing before stopping")); @@ -164,8 +213,8 @@ public async Task ShouldUseTheCorrectTitleForEventDrivenCallbacks() IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions()).ConfigureAwait(false); await page.RouteAsync("**/empty.html", route => route.ContinueAsync()).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/empty.html").ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/grid.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/empty.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/grid.html").ConfigureAwait(false); await page.EvaluateAsync("() => alert('yo')").ConfigureAwait(false); await page.ReloadAsync().ConfigureAwait(false); page.Dialog += (_, dialog) => @@ -213,7 +262,7 @@ public async Task ShouldNotCollectSnapshotsByDefault() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions()).ConfigureAwait(false); - await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(EmptyPage).ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); await page.ClickAsync("\"Click\"").ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); @@ -247,7 +296,7 @@ public async Task ShouldNotCollectActionScreenshotsAndAriaSnapshotsByDefault() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Snapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/input/button.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/input/button.html").ConfigureAwait(false); await page.ClickAsync("button").ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = path }).ConfigureAwait(false); @@ -279,7 +328,7 @@ public async Task ShouldCollectActionScreenshots() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { ScreenSnapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/input/button.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/input/button.html").ConfigureAwait(false); await page.ClickAsync("button").ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = path }).ConfigureAwait(false); @@ -326,7 +375,7 @@ public async Task ShouldCollectAriaSnapshots() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { AriaSnapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/input/button.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/input/button.html").ConfigureAwait(false); await page.ClickAsync("button").ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = path }).ConfigureAwait(false); @@ -377,7 +426,7 @@ public async Task CanCallTracingGroupGroupEndAtAnyTimeAndAutoClose() await context.Tracing.GroupAsync("ignored2").ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions()).ConfigureAwait(false); await context.Tracing.GroupAsync("actual").ConfigureAwait(false); - await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(EmptyPage).ConfigureAwait(false); await context.Tracing.StopChunkAsync(path).ConfigureAwait(false); await context.Tracing.GroupAsync("ignored3").ConfigureAwait(false); await context.Tracing.GroupEndAsync().ConfigureAwait(false); @@ -435,7 +484,7 @@ public async Task ShouldNotIncludeBuffersInTheTrace() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Snapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/empty.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/empty.html").ConfigureAwait(false); await page.ScreenshotAsync().ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = path }).ConfigureAwait(false); @@ -484,7 +533,7 @@ public async Task ShouldExcludeInternalPages() await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); - await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(EmptyPage).ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions()).ConfigureAwait(false); await context.StorageStateAsync().ConfigureAwait(false); await page.CloseAsync().ConfigureAwait(false); @@ -532,7 +581,7 @@ public async Task ShouldRecordContextApiRequestTraceIndependently() string browserTracePath = TempZip(); string apiTracePath = TempZip(); - string apiUrl = TestConstants.ServerUrl + "/simple.json"; + string apiUrl = Prefix + "/simple.json"; try { await using IBrowser browser = await BrowserLauncher.LaunchAsync().ConfigureAwait(false); @@ -542,7 +591,7 @@ public async Task ShouldRecordContextApiRequestTraceIndependently() await context.Tracing.StartAsync(new TracingStartOptions { Snapshots = true }).ConfigureAwait(false); await context.APIRequest.Tracing.StartAsync(new TracingStartOptions { Snapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/one-style.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/one-style.html").ConfigureAwait(false); await page.APIRequest.PostAsync(apiUrl, new() { DataObject = new { foo = "bar" } }).ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = browserTracePath }).ConfigureAwait(false); await context.APIRequest.Tracing.StopAsync(new TracingStopOptions { Path = apiTracePath }).ConfigureAwait(false); @@ -602,7 +651,7 @@ public async Task ShouldCollectTwoTraces() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Screenshots = true, Snapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(EmptyPage).ConfigureAwait(false); await page.SetContentAsync("").ConfigureAwait(false); await page.ClickAsync("\"Click\"").ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = first }).ConfigureAwait(false); @@ -656,13 +705,13 @@ public async Task ShouldRespectTracesDirAndName() await using IBrowserContext context = await browser.NewContextAsync().ConfigureAwait(false); IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Name = "name1", Snapshots = true }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/one-style.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/one-style.html").ConfigureAwait(false); await context.Tracing.StopChunkAsync(first).ConfigureAwait(false); Assert.That(File.Exists(Path.Combine(tracesDir, "name1.trace")), Is.True); Assert.That(File.Exists(Path.Combine(tracesDir, "name1.network")), Is.True); await context.Tracing.StartChunkAsync(new() { Name = "name2" }).ConfigureAwait(false); - await page.GoToAsync(TestConstants.ServerUrl + "/har.html").ConfigureAwait(false); + await page.GoToAsync(Prefix + "/har.html").ConfigureAwait(false); await context.Tracing.StopAsync(new TracingStopOptions { Path = second }).ConfigureAwait(false); Assert.That(File.Exists(Path.Combine(tracesDir, "name2.trace")), Is.True); Assert.That(File.Exists(Path.Combine(tracesDir, "name2.network")), Is.True); @@ -718,7 +767,7 @@ public async Task ShouldNotIncludeTraceResourcesFromThePreviousChunks() IPage page = await context.NewPageAsync().ConfigureAwait(false); await context.Tracing.StartAsync(new TracingStartOptions { Screenshots = true, Snapshots = true, Sources = true }).ConfigureAwait(false); await context.Tracing.StartChunkAsync().ConfigureAwait(false); - await page.GoToAsync(TestConstants.EmptyPage).ConfigureAwait(false); + await page.GoToAsync(EmptyPage).ConfigureAwait(false); await page.SetContentAsync(@"