Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSOE-771: Adding more navigation methods #342

Merged
merged 9 commits into from
Feb 13, 2024
11 changes: 10 additions & 1 deletion Lombiq.Tests.UI.Samples/Tests/MonkeyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,17 @@ public Task TestCurrentPageAsMonkeyRecursivelyShouldWorkWithAnonymousUser() =>
public Task TestAdminPagesAsMonkeyRecursivelyShouldWorkWithAdminUser() =>
ExecuteTestAfterSetupAsync(
context =>
{
// Monkey tests needn't all start from the homepage. This one starts from the Orchard admin dashboard.
context.TestAdminAsMonkeyRecursivelyAsync(CreateMonkeyTestingOptions()),

var monkeyTestingOptions = CreateMonkeyTestingOptions();

// So we don't take too much time testing the whole Orchard admin, this sample restricts requests to
// "/Admin". But this is just this sample, you can unleash monkeys on the whole admin too!
monkeyTestingOptions.UrlFilters.Add(new MatchesRegexMonkeyTestingUrlFilter("/Admin$"));

return context.TestAdminAsMonkeyRecursivelyAsync(monkeyTestingOptions);
},
configuration =>
configuration.AssertBrowserLog = logEntries => logEntries.ShouldNotContain(
logEntry => IsValidAdminBrowserLogEntry(logEntry),
Expand Down
12 changes: 12 additions & 0 deletions Lombiq.Tests.UI/Extensions/ExtendedLoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ private static void ExecuteSection(this UITestContext context, LogSection sectio
}
catch (StaleElementReferenceException) when (notLast)
{
LogStaleElementReferenceExceptionRetry(context, i);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
Expand All @@ -122,6 +123,7 @@ private static TResult ExecuteSection<TResult>(this UITestContext context, LogSe
}
catch (StaleElementReferenceException) when (notLast)
{
LogStaleElementReferenceExceptionRetry(context, i);
Thread.Sleep(TimeSpan.FromSeconds(1));
}
}
Expand Down Expand Up @@ -150,16 +152,26 @@ private static async Task<TResult> ExecuteSectionAsync<TResult>(
// multiple sections are started in concurrent threads, the result will be incorrect. This shouldn't be too much
// of an issue for now though since tests, while async, are single-threaded.
context.Scope.AtataContext.Log.Start(section);
context.Scope.AtataContext.Log.Info("Log section {0} started.", section.Message);
var result = await functionAsync();
context.Scope.AtataContext.Log.Info("Log section {0} ended.", section.Message);
Comment on lines +155 to +157
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition of logging statements to indicate the start and end of a log section enhances the visibility of operations within tests. However, consider adding error logging within the catch block for StaleElementReferenceException to provide insights into failures that trigger retries.

catch (StaleElementReferenceException) when (notLast)
{
+   context.Scope.AtataContext.Log.Warn("Encountered a StaleElementReferenceException, retrying...");
    await Task.Delay(TimeSpan.FromSeconds(1));
}

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
context.Scope.AtataContext.Log.Info("Log section {0} started.", section.Message);
var result = await functionAsync();
context.Scope.AtataContext.Log.Info("Log section {0} ended.", section.Message);
context.Scope.AtataContext.Log.Info("Log section {0} started.", section.Message);
var result = await functionAsync();
context.Scope.AtataContext.Log.Info("Log section {0} ended.", section.Message);
catch (StaleElementReferenceException) when (notLast)
{
context.Scope.AtataContext.Log.Warn("Encountered a StaleElementReferenceException, retrying...");
await Task.Delay(TimeSpan.FromSeconds(1));
}

context.Scope.AtataContext.Log.EndSection();
return result;
}
catch (StaleElementReferenceException) when (notLast)
{
LogStaleElementReferenceExceptionRetry(context, i);
await Task.Delay(TimeSpan.FromSeconds(1));
}
}

throw new InvalidOperationException("Impossible to reach.");
}

private static void LogStaleElementReferenceExceptionRetry(UITestContext context, int tryIndex) =>
context.Scope.AtataContext.Log.Info(
"The operation in the log section failed with StaleElementReferenceException but will be retried. This " +
"is try number {0} out of {1}.",
tryIndex + 1,
StabilityRetryCount);
}
20 changes: 20 additions & 0 deletions Lombiq.Tests.UI/Extensions/NavigationUITestContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ private static async Task SetFieldDropdownByIndexAsync(UITestContext context, By
public static Task ClickReliablyOnAsync(this UITestContext context, By by, int maxTries = 3) =>
context.Get(by).ClickReliablyAsync(context, maxTries);

/// <summary>
/// Reliably clicks on the link identified by the given text with <see
/// cref="NavigationWebElementExtensions.ClickReliablyAsync(IWebElement, UITestContext, int)"/>.
/// </summary>
/// <param name="maxTries">The maximum number of clicks attempted altogether, if retries are needed.</param>
public static Task ClickReliablyOnByLinkTextAsync(this UITestContext context, string linkText, int maxTries = 3) =>
context.Get(By.LinkText(linkText)).ClickReliablyAsync(context, maxTries);

/// <summary>
/// A convenience method that merges <see cref="ElementRetrievalUITestContextExtensions.Get"/> and <see
/// cref="NavigationWebElementExtensions.ClickReliablyUntilPageLeaveAsync(IWebElement, UITestContext, TimeSpan?,
Expand All @@ -296,6 +304,18 @@ public static Task ClickReliablyOnUntilPageLeaveAsync(
TimeSpan? interval = null) =>
context.Get(by).ClickReliablyUntilPageLeaveAsync(context, timeout, interval);

/// <summary>
/// A convenience method that merges <see cref="ElementRetrievalUITestContextExtensions.Get"/> and <see
/// cref="NavigationWebElementExtensions.ClickReliablyUntilUrlChangeAsync(IWebElement, UITestContext, TimeSpan?,
/// TimeSpan?)"/> so the <paramref name="context"/> doesn't have to be passed twice.
/// </summary>
public static Task ClickReliablyOnUntilUrlChangeAsync(
this UITestContext context,
By by,
TimeSpan? timeout = null,
TimeSpan? interval = null) =>
context.Get(by).ClickReliablyUntilUrlChangeAsync(context, timeout, interval);
Piedone marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Switches control to JS alert box, accepts it, and switches control back to main document or first frame.
/// </summary>
Expand Down
32 changes: 30 additions & 2 deletions Lombiq.Tests.UI/Extensions/NavigationWebElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,11 @@ await context.Configuration.Events.AfterClick
});

/// <summary>
/// Repeatedly clicks an element until the browser leaves the page. If you're doing a Get() before then use <see
/// cref="NavigationUITestContextExtensions.ClickReliablyOnAsync(UITestContext, By, int)"/> instead.
/// Repeatedly clicks an element until the browser leaves the page. Note that unlike <see
/// cref="ClickReliablyUntilUrlChangeAsync"/> this doesn't just necessitate a URL change but also a page leave. If
/// you're doing a Get() before then use <see
/// cref="NavigationUITestContextExtensions.ClickReliablyOnUntilPageLeaveAsync(UITestContext, By, TimeSpan?,
/// TimeSpan?)"/> instead.
/// </summary>
public static Task ClickReliablyUntilPageLeaveAsync(
this IWebElement element,
Expand All @@ -82,4 +85,29 @@ public static Task ClickReliablyUntilPageLeaveAsync(
},
timeout,
interval);

/// <summary>
/// Repeatedly clicks an element until the browser URL changes. Note that unlike <see
/// cref="ClickReliablyUntilPageLeaveAsync"/> this doesn't necessitate a page leave, but can include it. If you're
/// doing a Get() before then use <see
/// cref="NavigationUITestContextExtensions.ClickReliablyOnUntilUrlChangeAsync(UITestContext, By, TimeSpan?,
/// TimeSpan?)"/> instead.
/// </summary>
public static Task ClickReliablyUntilUrlChangeAsync(
this IWebElement element,
UITestContext context,
TimeSpan? timeout = null,
TimeSpan? interval = null)
{
var originalUri = context.GetCurrentUri();

return context.DoWithRetriesOrFailAsync(
async () =>
{
await element.ClickReliablyAsync(context);
return context.GetCurrentUri() != originalUri;
},
timeout,
interval);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ public static async Task ClickNewContentItemAsync(this UITestContext context, st
if (dropdown)
{
await context.ClickReliablyOnAsync(By.Id("new-dropdown"));
await context.ClickReliablyOnAsync(By.LinkText(contentItemName));
await context.ClickReliablyOnByLinkTextAsync(contentItemName);
}
else
{
await context.ClickReliablyOnAsync(By.LinkText($"New {contentItemName}"));
await context.ClickReliablyOnByLinkTextAsync($"New {contentItemName}");
}
}

Expand Down
4 changes: 2 additions & 2 deletions Lombiq.Tests.UI/Extensions/TenantsUITestContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static async Task CreateAndSwitchToTenantManuallyAsync(
{
await context.CreateTenantManuallyAsync(name, urlPrefix, urlHost, featureProfile, navigate);

await context.ClickReliablyOnAsync(By.LinkText("Setup"));
await context.ClickReliablyOnByLinkTextAsync("Setup");
Piedone marked this conversation as resolved.
Show resolved Hide resolved

context.SwitchCurrentTenant(name, urlPrefix);
}
Expand All @@ -34,7 +34,7 @@ public static async Task CreateTenantManuallyAsync(
await context.GoToAdminRelativeUrlAsync("/Tenants");
}

await context.ClickReliablyOnAsync(By.LinkText("Add Tenant"));
await context.ClickReliablyOnByLinkTextAsync("Add Tenant");
await context.ClickAndFillInWithRetriesAsync(By.Id("Name"), name);

if (!string.IsNullOrEmpty(urlPrefix))
Expand Down