diff --git a/tests/Motus.Tests/Browser/FirefoxIntegrationTests.cs b/tests/Motus.Tests/Browser/FirefoxIntegrationTests.cs index 73aabee..d6622bd 100644 --- a/tests/Motus.Tests/Browser/FirefoxIntegrationTests.cs +++ b/tests/Motus.Tests/Browser/FirefoxIntegrationTests.cs @@ -209,7 +209,20 @@ await page.SetContentAsync( Assert.IsTrue(await page.EvaluateAsync(Run("b")), "The script did not scroll to an element below the fold."); Assert.IsTrue(await page.EvaluateAsync(InView), "The element is still outside the viewport."); - var scrollY = await page.EvaluateAsync("window.scrollY"); + // Centering lands on a half pixel whenever the viewport's height is odd, and Firefox can + // round a half-pixel scroll position to a whole one after the scroll has finished, so a + // position read straight after centering can move by a pixel with nothing scrolling it. + // The element is put at a whole-pixel position off center instead: still in view, so the + // script must leave it alone, and far enough from center that a script that scrolled + // anyway would visibly move it, which re-centering an already centered element would not. + var scrollY = await page.EvaluateAsync(""" + (() => { + const top = document.getElementById('b').getBoundingClientRect().top + window.scrollY; + window.scrollTo({ top: Math.round(top) - 100, behavior: 'instant' }); + return window.scrollY; + })() + """); + Assert.IsTrue(await page.EvaluateAsync(InView), "The element left the viewport when it was moved off center."); Assert.IsFalse(await page.EvaluateAsync(Run("b")), "The script scrolled an element that was already in view."); Assert.AreEqual(scrollY, await page.EvaluateAsync("window.scrollY")); diff --git a/tests/Motus.Tests/Context/TracingTests.cs b/tests/Motus.Tests/Context/TracingTests.cs index ad6dca4..29d7c68 100644 --- a/tests/Motus.Tests/Context/TracingTests.cs +++ b/tests/Motus.Tests/Context/TracingTests.cs @@ -296,16 +296,27 @@ public async Task StartAsync_ConcurrentOnSameBrowser_SerializesViaGate() await Task.Delay(50); Assert.IsFalse(secondStartTask.IsCompleted, "Second StartAsync should be gated until the first stops."); - // Now stop the first and let the second proceed. + // Now stop the first and let the second proceed. The second start's answer is queued + // here, before the stop, because the stop releases the gate on its way out and the + // second start sends Tracing.start from that moment on another thread. Queued after + // awaiting the stop, the answer could arrive after the command it was meant for, which + // then waits out the transport's command timeout. The gate is what keeps the two + // answers in order: nothing can send Tracing.start until Tracing.end has been answered. _socket.QueueResponse("""{"id":2,"result":{}}"""); // first end + _socket.QueueResponse("""{"id":3,"result":{}}"""); // second start, once the gate opens var firstStopTask = first.StopAsync(); await Task.Delay(50); _socket.Enqueue("""{"method":"Tracing.tracingComplete","params":{"dataLossOccurred":false}}"""); await firstStopTask; - _socket.QueueResponse("""{"id":3,"result":{}}"""); // second start (now unblocked) await secondStartTask; + var sent = _socket.SentMessages.Select(bytes => System.Text.Encoding.UTF8.GetString(bytes)).ToList(); + var firstEnd = sent.FindIndex(json => json.Contains("\"Tracing.end\"")); + var secondStart = sent.FindLastIndex(json => json.Contains("\"Tracing.start\"")); + Assert.IsTrue(firstEnd >= 0 && secondStart > firstEnd, + "The second Tracing.start went out before the first trace had ended."); + // Cleanly stop the second so the gate is released for any teardown. _socket.QueueResponse("""{"id":4,"result":{}}"""); var secondStopTask = second.StopAsync();