Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions CSF.Screenplay.Selenium/Tasks/ClickAndWaitForDocumentReady.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ public class ClickAndWaitForDocumentReady : ISingleElementPerformable
const string COMPLETE_READY_STATE = "complete";

static readonly NamedScriptWithResult<string> getReadyState = Scripts.GetTheDocumentReadyState;
static readonly TimeSpan
pollingInterval = TimeSpan.FromMilliseconds(100),
stalenessTimeout = TimeSpan.FromMilliseconds(500);

readonly TimeSpan waitTimeout;

Expand All @@ -39,11 +36,10 @@ public async ValueTask PerformAsAsync(ICanPerform actor, IWebDriver webDriver, L
{
await actor.PerformAsync(ClickOn(element.Value), cancellationToken);
await actor.PerformAsync(WaitUntil(ElementIsStale(element.Value.WebElement))
.ForAtMost(stalenessTimeout)
.WithPollingInterval(pollingInterval)
.ForAtMost(waitTimeout)
.Named($"{element.Value.Name} is no longer on the page"),
cancellationToken);
await actor.PerformAsync(WaitUntil(PageIsReady).ForAtMost(waitTimeout).Named("the page is ready").WithPollingInterval(pollingInterval),
await actor.PerformAsync(WaitUntil(PageIsReady).ForAtMost(waitTimeout).Named("the page is ready"),
cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

public class DelayedOpeningController : Controller

Check warning on line 5 in Tests/CSF.Screenplay.Selenium.TestWebapp/Controllers/DelayedOpeningController.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Specify the RouteAttribute when an HttpMethodAttribute or RouteAttribute is specified at an action level. (https://rules.sonarsource.com/csharp/RSPEC-6934)

Check warning on line 5 in Tests/CSF.Screenplay.Selenium.TestWebapp/Controllers/DelayedOpeningController.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Move 'DelayedOpeningController' into a named namespace. (https://rules.sonarsource.com/csharp/RSPEC-3903)

Check warning on line 5 in Tests/CSF.Screenplay.Selenium.TestWebapp/Controllers/DelayedOpeningController.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Specify the RouteAttribute when an HttpMethodAttribute or RouteAttribute is specified at an action level. (https://rules.sonarsource.com/csharp/RSPEC-6934)

Check warning on line 5 in Tests/CSF.Screenplay.Selenium.TestWebapp/Controllers/DelayedOpeningController.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

Move 'DelayedOpeningController' into a named namespace. (https://rules.sonarsource.com/csharp/RSPEC-3903)
{
[HttpGet, Route("DelayedOpening")]
public async Task<ViewResult> Index()
public async Task<ViewResult> Index(int delaySeconds = 2)

Check warning on line 8 in Tests/CSF.Screenplay.Selenium.TestWebapp/Controllers/DelayedOpeningController.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)

Check warning on line 8 in Tests/CSF.Screenplay.Selenium.TestWebapp/Controllers/DelayedOpeningController.cs

View workflow job for this annotation

GitHub Actions / Build, test & package

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
{
await Task.Delay(TimeSpan.FromSeconds(2));
return View();
await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
return View(delaySeconds);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<body>
<h1>Delayed navigation landing</h1>
<p>
This is the page which takes 2 seconds to load.
This is the page which takes @Model second(s) to load.
</p>
<p id="textContent">You're finally here!</p>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ <h1>Delayed navigation</h1>
<p>
Click the link below to trigger navigation to a page which takes a few moments before it becomes available.
</p>
<a href="/DelayedOpening" id="clickable">Click me</a>
<label for="delaySeconds">Delay loading by (seconds)</label><input type="number" id="delaySeconds" value="2">
<a href="/DelayedOpening?delaySeconds=2" id="clickable">Click me</a>
</body>
<script>
window.onload = function() {
document.getElementById("delaySeconds").addEventListener('change', (ev) => {
document.getElementById("clickable").setAttribute("href", "/DelayedOpening?delaySeconds=" + ev.currentTarget.value);
});
}
</script>
</html>
79 changes: 43 additions & 36 deletions Tests/CSF.Screenplay.Selenium.Tests/Actions/WaitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System;
using CSF.Screenplay.Performables;
using CSF.Screenplay.Selenium.Elements;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using static CSF.Screenplay.PerformanceStarter;
using static CSF.Screenplay.Selenium.PerformableBuilder;

Expand All @@ -18,21 +20,28 @@ static readonly ITarget

static readonly NamedUri testPage = new NamedUri("WaitTests.html", "the test page");

static int GetDelayMilliseconds(Actor actor)
=> actor.GetAbility<BrowseTheWeb>().WebDriver.Unproxy() is RemoteWebDriver ? 5000 : 2000;

static int GetSufficientWaitMilliseconds(Actor actor)
=> actor.GetAbility<BrowseTheWeb>().WebDriver.Unproxy() is RemoteWebDriver ? 9000 : 4000;

static int GetInsufficientWaitMilliseconds(Actor actor) => 500;

[Test, Screenplay]
public async Task WaitingForSufficientTimeShouldSucceed(IStage stage)
{
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
await Then(webster).Should(WaitUntil(displayText.Has().Text("Clicked, and 250ms has elapsed"))
.ForAtMost(TimeSpan.FromMilliseconds(500))
.WithPollingInterval(TimeSpan.FromMilliseconds(150))
await Then(webster).Should(WaitUntil(displayText.Has().Text($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"))
.ForAtMost(TimeSpan.FromMilliseconds(GetSufficientWaitMilliseconds(webster)))
);
var contents = await Then(webster).Should(ReadFromTheElement(displayText).TheText());

Assert.That(contents, Is.EqualTo("Clicked, and 250ms has elapsed"));
Assert.That(contents, Is.EqualTo($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"));
}

[Test, Screenplay]
Expand All @@ -41,16 +50,15 @@ public async Task WaitingForSufficientTimeWithIgnoredExceptionsShouldSucceed(ISt
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
await Then(webster).Should(WaitUntil(displayTextSpan.Has().Text("250"))
.ForAtMost(TimeSpan.FromMilliseconds(500))
.WithPollingInterval(TimeSpan.FromMilliseconds(50))
await Then(webster).Should(WaitUntil(displayTextSpan.Has().Text(GetDelayMilliseconds(webster).ToString()))
.ForAtMost(TimeSpan.FromMilliseconds(GetSufficientWaitMilliseconds(webster)))
.IgnoringTheseExceptionTypes(typeof(TargetNotFoundException))
);
var contents = await Then(webster).Should(ReadFromTheElement(displayText).TheText());

Assert.That(contents, Is.EqualTo("Clicked, and 250ms has elapsed"));
Assert.That(contents, Is.EqualTo($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"));
}

[Test, Screenplay]
Expand All @@ -59,16 +67,15 @@ public async Task WaitingForSufficientTimeWithoutIgnoredExceptionsShouldSucceed(
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
await Then(webster).Should(WaitUntil(displayTextSpan.Has().Text("250"))
.ForAtMost(TimeSpan.FromMilliseconds(500))
.WithPollingInterval(TimeSpan.FromMilliseconds(50))
await Then(webster).Should(WaitUntil(displayTextSpan.Has().Text(GetDelayMilliseconds(webster).ToString()))
.ForAtMost(TimeSpan.FromMilliseconds(GetSufficientWaitMilliseconds(webster)))
// No ignored exceptions specified here, but the default behaviour will ignore TargetNotFoundException
);
var contents = await Then(webster).Should(ReadFromTheElement(displayText).TheText());

Assert.That(contents, Is.EqualTo("Clicked, and 250ms has elapsed"));
Assert.That(contents, Is.EqualTo($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"));
}

[Test, Screenplay]
Expand All @@ -77,11 +84,10 @@ public async Task WaitingForSufficientTimeWithEmptyIgnoredExceptionsShouldThrow(
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
Assert.That(async () => await Then(webster).Should(WaitUntil(displayTextSpan.Has().Text("250"))
.ForAtMost(TimeSpan.FromMilliseconds(500))
.WithPollingInterval(TimeSpan.FromMilliseconds(50))
Assert.That(async () => await Then(webster).Should(WaitUntil(displayTextSpan.Has().Text(GetDelayMilliseconds(webster).ToString()))
.ForAtMost(TimeSpan.FromMilliseconds(GetSufficientWaitMilliseconds(webster)))
.IgnoringTheseExceptionTypes() // Explicitly empty
), Throws.InstanceOf<PerformableException>());
}
Expand All @@ -92,40 +98,41 @@ public async Task WaitingForInsufficientTimeShouldThrow(IStage stage)
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("2000").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));

Assert.That(async () => await Then(webster).Should(WaitUntil(displayText.Has().Text("Clicked, and 2000ms has elapsed")).ForAtMost(TimeSpan.FromMilliseconds(500))),
Throws.InstanceOf<PerformableException>().And.InnerException.TypeOf<OpenQA.Selenium.WebDriverTimeoutException>());
Assert.That(async () => await Then(webster).Should(WaitUntil(displayText.Has().Text($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"))
.ForAtMost(TimeSpan.FromMilliseconds(GetInsufficientWaitMilliseconds(webster)))),
Throws.InstanceOf<PerformableException>().And.InnerException.TypeOf<WebDriverTimeoutException>());
}

[Test, Screenplay]
public async Task WaitingForSufficientTimeUsingDefaultWaitAbilityShouldSucceed(IStage stage)
{
var webster = stage.Spotlight<Webster>();
webster.IsAbleTo(new UseADefaultWaitTime(TimeSpan.FromMilliseconds(500)));
webster.IsAbleTo(new UseADefaultWaitTime(TimeSpan.FromMilliseconds(GetSufficientWaitMilliseconds(webster))));

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
await Then(webster).Should(WaitUntil(displayText.Has().Text("Clicked, and 250ms has elapsed")));
await Then(webster).Should(WaitUntil(displayText.Has().Text($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed")));
var contents = await Then(webster).Should(ReadFromTheElement(displayText).TheText());

Assert.That(contents, Is.EqualTo("Clicked, and 250ms has elapsed"));
Assert.That(contents, Is.EqualTo($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"));
}

[Test, Screenplay]
public async Task WaitingForInsufficientTimeUsingDefaultWaitAbilityShouldThrow(IStage stage)
{
var webster = stage.Spotlight<Webster>();
webster.IsAbleTo(new UseADefaultWaitTime(TimeSpan.FromMilliseconds(100)));
webster.IsAbleTo(new UseADefaultWaitTime(TimeSpan.FromMilliseconds(GetInsufficientWaitMilliseconds(webster))));

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("2000").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));

Assert.That(async () => await Then(webster).Should(WaitUntil(displayText.Has().Text("Clicked, and 2000ms has elapsed"))),
Throws.InstanceOf<PerformableException>().And.InnerException.TypeOf<OpenQA.Selenium.WebDriverTimeoutException>());
Assert.That(async () => await Then(webster).Should(WaitUntil(displayText.Has().Text($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"))),
Throws.InstanceOf<PerformableException>().And.InnerException.TypeOf<WebDriverTimeoutException>());
}

[Test, Screenplay]
Expand All @@ -134,12 +141,12 @@ public async Task WaitingForSufficientTimeWithoutAPredicateShouldSucceed(IStage
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
await Then(webster).Should(WaitFor(TimeSpan.FromMilliseconds(300)));
await Then(webster).Should(WaitFor(TimeSpan.FromMilliseconds(GetSufficientWaitMilliseconds(webster))));
var contents = await Then(webster).Should(ReadFromTheElement(displayText).TheText());

Assert.That(contents, Is.EqualTo("Clicked, and 250ms has elapsed"));
Assert.That(contents, Is.EqualTo($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"));
}

[Test, Screenplay]
Expand All @@ -148,11 +155,11 @@ public async Task WaitingForInsufficientTimeWithoutAPredicateShouldYieldIncorrec
var webster = stage.Spotlight<Webster>();

await Given(webster).WasAbleTo(OpenTheUrl(testPage));
await Given(webster).WasAbleTo(EnterTheText("250").Into(delayTimer));
await Given(webster).WasAbleTo(EnterTheText(GetDelayMilliseconds(webster).ToString()).Into(delayTimer));
await When(webster).AttemptsTo(ClickOn(clickableButton));
await Then(webster).Should(WaitFor(TimeSpan.FromMilliseconds(10)));
await Then(webster).Should(WaitFor(TimeSpan.FromMilliseconds(GetInsufficientWaitMilliseconds(webster))));
var contents = await Then(webster).Should(ReadFromTheElement(displayText).TheText());

Assert.That(contents, Is.Not.EqualTo("Clicked, and 250ms has elapsed"));
Assert.That(contents, Is.Not.EqualTo($"Clicked, and {GetDelayMilliseconds(webster)}ms has elapsed"));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using CSF.Screenplay.Performables;
using CSF.Screenplay.Selenium.Elements;
using OpenQA.Selenium;
Expand All @@ -15,6 +16,8 @@ static readonly ITarget
link = new ElementId("clickable"),
displayText = new ElementId("textContent");

static readonly string[] ignoredBrowsers = ["chrome", "MicrosoftEdge"];

[Test, Screenplay]
public async Task PerformAsAsyncShouldWaitSoItCanGetTheAppropriateContent(IStage stage)
{
Expand All @@ -33,12 +36,12 @@ public async Task PerformAsAsyncShouldThrowIfWeDontWaitLongEnough(IStage stage)
var webster = stage.Spotlight<Webster>();
var ability = webster.GetAbility<BrowseTheWeb>();

if(ability.DriverOptions.BrowserName == "chrome")
Assert.Pass("This test cannot meaningfully be run on a Chrome browser, because it always waits for the page load; treating as an implicit pass");
if(ignoredBrowsers.Contains(ability.DriverOptions.BrowserName))
Assert.Pass("This test cannot meaningfully be run on a Chrome or Edge browser, because they always wait for the page load. Treating this test as an implicit pass.");

await Given(webster).WasAbleTo(OpenTheUrl(startPage));

Assert.That(async () => await When(webster).AttemptsTo(ClickOn(link).AndWaitForANewPageToLoad(TimeSpan.FromMilliseconds(200))),
Assert.That(async () => await When(webster).AttemptsTo(ClickOn(link).AndWaitForANewPageToLoad(TimeSpan.FromMilliseconds(100))),
Throws.InstanceOf<PerformableException>().And.InnerException.InstanceOf<WebDriverException>());
}
}
Loading