mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 20:15:46 -06:00
Fix for WebAuthnSigningInTest WebAuthn test
Closes #43477 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
@@ -22,14 +22,15 @@ import java.util.LinkedList;
|
||||
import org.keycloak.testsuite.webauthn.pages.fragments.ContentAlert;
|
||||
import org.keycloak.testsuite.webauthn.pages.fragments.ContinueCancelModal;
|
||||
import org.keycloak.testsuite.webauthn.pages.fragments.LoggedInPageHeader;
|
||||
import org.keycloak.testsuite.webauthn.pages.fragments.Sidebar;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.support.FindBy;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.UIUtils.getTextFromElement;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
@@ -40,9 +41,6 @@ public abstract class AbstractLoggedInPage extends AbstractAccountPage {
|
||||
@FindBy(xpath = "//*[@data-testid='page-header']")
|
||||
private LoggedInPageHeader header;
|
||||
|
||||
@FindBy(id = "page-sidebar")
|
||||
private Sidebar sidebar;
|
||||
|
||||
@Page
|
||||
private ContentAlert alert;
|
||||
|
||||
@@ -71,6 +69,12 @@ public abstract class AbstractLoggedInPage extends AbstractAccountPage {
|
||||
*/
|
||||
public abstract String getPageId();
|
||||
|
||||
/**
|
||||
* Title of the account page translated into english. For example "Signing in" or "Device activity".
|
||||
* @return The translated page title
|
||||
*/
|
||||
public abstract String getTranslatedPageTitle();
|
||||
|
||||
/**
|
||||
* In case the page is placed is a subpage, i.e. placed in a subsection. See content.json in themes module for IDs.
|
||||
*
|
||||
@@ -80,30 +84,20 @@ public abstract class AbstractLoggedInPage extends AbstractAccountPage {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should simulate a user navigating to this page using links in the nav bar. It assume that user is logged in
|
||||
* and at some Account Console page (not Welcome Screen), i.e. that the nav bar is visible.
|
||||
*/
|
||||
public void navigateToUsingSidebar() {
|
||||
if (sidebar.isCollapsed()) {
|
||||
sidebar.expand();
|
||||
}
|
||||
final String dataTestId = String.join("/", hashPath);
|
||||
clickLink(driver.findElement(By.xpath("//*[@data-testid='" + dataTestId + "']")));
|
||||
waitForPageTitle();
|
||||
}
|
||||
|
||||
if (getParentPageId() != null) {
|
||||
sidebar().clickSubNav(getParentPageId(), getPageId());
|
||||
} else {
|
||||
sidebar().clickNav(getPageId());
|
||||
}
|
||||
public void waitForPageTitle() {
|
||||
waitUntilElement(By.xpath("//*[@data-testid='page-heading']")).text().equalTo(getTranslatedPageTitle());
|
||||
}
|
||||
|
||||
public LoggedInPageHeader header() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public Sidebar sidebar() {
|
||||
return sidebar;
|
||||
}
|
||||
|
||||
public ContentAlert alert() {
|
||||
return alert;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2025 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.webauthn.pages;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class DeviceActivityPage extends AbstractLoggedInPage {
|
||||
|
||||
@Override
|
||||
public String getPageId() {
|
||||
return "device-activity";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentPageId() {
|
||||
return ACCOUNT_SECURITY_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslatedPageTitle() {
|
||||
return "Device activity";
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,11 @@ public class SigningInPage extends AbstractLoggedInPage {
|
||||
return ACCOUNT_SECURITY_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTranslatedPageTitle() {
|
||||
return "Signing in";
|
||||
}
|
||||
|
||||
public CredentialType getCredentialType(String type) {
|
||||
return new CredentialType(type);
|
||||
}
|
||||
@@ -150,6 +155,10 @@ public class SigningInPage extends AbstractLoggedInPage {
|
||||
public String getHelpText() {
|
||||
return getTextFromElement(getItemElementByTestId(HELP));
|
||||
}
|
||||
|
||||
public void navigateToUsingSidebar() {
|
||||
SigningInPage.this.navigateToUsingSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
public class UserCredential {
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.webauthn.pages.fragments;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||
import org.jboss.arquillian.graphene.fragment.Root;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.NoSuchElementException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.clickLink;
|
||||
import static org.keycloak.testsuite.util.WaitUtils.pause;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public class Sidebar extends AbstractFragmentWithMobileLayout {
|
||||
public static int MOBILE_WIDTH = 767; // if the page width is less or equal than this, we expect the sidebar to be collapsed by default
|
||||
public static final String NAV_ITEM_ID_PREFIX = "nav-link-";
|
||||
|
||||
@Drone
|
||||
protected WebDriver driver;
|
||||
|
||||
@Root
|
||||
private WebElement sidebarRoot;
|
||||
|
||||
@Override
|
||||
protected int getMobileWidth() {
|
||||
return MOBILE_WIDTH;
|
||||
}
|
||||
|
||||
public boolean isCollapsed() {
|
||||
return sidebarRoot.getAttribute("class").contains("collapsed");
|
||||
}
|
||||
|
||||
public void collapse() {
|
||||
assertFalse("Sidebar is already collapsed", isCollapsed());
|
||||
getCollapseToggle().click();
|
||||
pause(2000); // wait for animation
|
||||
assertTrue("Sidebar is not collapsed", isCollapsed());
|
||||
}
|
||||
|
||||
public void expand() {
|
||||
assertTrue("Sidebar is already expanded", isCollapsed());
|
||||
getCollapseToggle().click();
|
||||
pause(2000); // wait for animation
|
||||
assertFalse("Sidebar is not expanded", isCollapsed());
|
||||
}
|
||||
|
||||
private WebElement getCollapseToggle(){
|
||||
return driver.findElement(By.id("nav-toggle"));
|
||||
}
|
||||
|
||||
protected void performOperationWithSidebarExpanded(Runnable operation) {
|
||||
if (isMobileLayout()) expand();
|
||||
operation.run();
|
||||
if (isMobileLayout()) collapse();
|
||||
}
|
||||
|
||||
protected WebElement getNavElement(String id) {
|
||||
return sidebarRoot.findElement(By.id(NAV_ITEM_ID_PREFIX + id));
|
||||
}
|
||||
|
||||
public void assertNavNotPresent(String id) {
|
||||
try {
|
||||
getNavElement(id).isDisplayed();
|
||||
throw new AssertionError("Nav element " + id + " shouldn't be present");
|
||||
}
|
||||
catch (NoSuchElementException e) {
|
||||
// ok
|
||||
}
|
||||
}
|
||||
|
||||
protected WebElement getNavSubsection(WebElement parent) {
|
||||
return parent.findElement(By.xpath("../section[@aria-labelledby='" + parent.getAttribute("id") + "']"));
|
||||
}
|
||||
|
||||
public void clickNav(String id) {
|
||||
performOperationWithSidebarExpanded(() -> clickLink(getNavElement(id)));
|
||||
}
|
||||
|
||||
public void clickSubNav(String parentId, String id) {
|
||||
performOperationWithSidebarExpanded(() -> {
|
||||
WebElement parentNavItem = getNavElement(parentId);
|
||||
if (!isNavSubsectionExpanded(parentNavItem)) {
|
||||
parentNavItem.click();
|
||||
}
|
||||
WebElement navItem = getNavSubsection(parentNavItem).findElement(By.id(NAV_ITEM_ID_PREFIX + id));
|
||||
clickLink(navItem);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isNavSubsectionExpanded(String parentId) {
|
||||
return isNavSubsectionExpanded(getNavElement(parentId));
|
||||
}
|
||||
|
||||
protected boolean isNavSubsectionExpanded(WebElement parent) {
|
||||
return getNavSubsection(parent).getAttribute("hidden") == null;
|
||||
}
|
||||
|
||||
public String getActiveNavId() {
|
||||
List<WebElement> activeNavElements = sidebarRoot.findElements(
|
||||
By.xpath("//*[starts-with(@id,'" + NAV_ITEM_ID_PREFIX + "') and contains(@class,'current')]")
|
||||
);
|
||||
|
||||
assertEquals("There are more than 1 active nav items", 1, activeNavElements.size());
|
||||
|
||||
return activeNavElements.get(0).getAttribute("id").split(NAV_ITEM_ID_PREFIX)[1];
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,9 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.testsuite.pages.DeleteCredentialPage;
|
||||
import org.keycloak.testsuite.webauthn.pages.AbstractLoggedInPage;
|
||||
import org.keycloak.testsuite.webauthn.pages.DeviceActivityPage;
|
||||
import org.keycloak.testsuite.webauthn.pages.SigningInPage;
|
||||
|
||||
import static org.keycloak.testsuite.util.UIUtils.refreshPageAndWaitForLoad;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
@@ -66,18 +65,25 @@ public class SigningInPageUtils {
|
||||
assertThat("Update button visible", userCredential.isUpdateBtnDisplayed(), is(not(removable)));
|
||||
}
|
||||
|
||||
public static void testSetUpLink(RealmResource realmResource, SigningInPage.CredentialType credentialType, String requiredActionProviderId) {
|
||||
public static void testSetUpLink(RealmResource realmResource, SigningInPage.CredentialType credentialType,
|
||||
String requiredActionProviderId, DeviceActivityPage deviceActivityPage) {
|
||||
assertThat("Set up link for \"" + credentialType.getType() + "\" is not visible", credentialType.isSetUpLinkVisible(), is(true));
|
||||
|
||||
RequiredActionProviderRepresentation requiredAction = new RequiredActionProviderRepresentation();
|
||||
RequiredActionProviderRepresentation requiredAction = realmResource.flows().getRequiredAction(requiredActionProviderId);
|
||||
requiredAction.setEnabled(false);
|
||||
realmResource.flows().updateRequiredAction(requiredActionProviderId, requiredAction);
|
||||
|
||||
refreshPageAndWaitForLoad();
|
||||
try {
|
||||
deviceActivityPage.navigateToUsingSidebar();
|
||||
credentialType.navigateToUsingSidebar();
|
||||
|
||||
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isSetUpLinkVisible(), is(false));
|
||||
assertThat("Title for \"" + credentialType.getType() + "\" is visible", credentialType.isTitleVisible(), is(false));
|
||||
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isNotSetUpLabelVisible(), is(false));
|
||||
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isSetUpLinkVisible(), is(false));
|
||||
assertThat("Title for \"" + credentialType.getType() + "\" is visible", credentialType.isTitleVisible(), is(false));
|
||||
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isNotSetUpLabelVisible(), is(false));
|
||||
} finally {
|
||||
requiredAction.setEnabled(true);
|
||||
realmResource.flows().updateRequiredAction(requiredActionProviderId, requiredAction);
|
||||
}
|
||||
}
|
||||
|
||||
public static void testRemoveCredential(AbstractLoggedInPage accountPage, DeleteCredentialPage deleteCredentialPage, SigningInPage.UserCredential userCredential) {
|
||||
|
||||
@@ -123,7 +123,7 @@ public abstract class AbstractWebAuthnAccountTest extends AbstractAuthTest imple
|
||||
signingInPage.navigateTo();
|
||||
waitForPageToLoad();
|
||||
loginToAccount();
|
||||
signingInPage.assertCurrent();
|
||||
signingInPage.waitForPageTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.IgnoreBrowserDriver;
|
||||
import org.keycloak.testsuite.page.AbstractPatternFlyAlert;
|
||||
import org.keycloak.testsuite.webauthn.authenticators.DefaultVirtualAuthOptions;
|
||||
import org.keycloak.testsuite.webauthn.pages.DeviceActivityPage;
|
||||
import org.keycloak.testsuite.webauthn.pages.SigningInPage;
|
||||
import org.keycloak.testsuite.webauthn.pages.WebAuthnAuthenticatorsList;
|
||||
import org.keycloak.testsuite.webauthn.updaters.AbstractWebAuthnRealmUpdater;
|
||||
@@ -46,6 +47,7 @@ import org.keycloak.testsuite.webauthn.updaters.WebAuthnRealmAttributeUpdater;
|
||||
import org.keycloak.theme.DateTimeFormatterUtil;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Test;
|
||||
import org.openqa.selenium.firefox.FirefoxDriver;
|
||||
|
||||
@@ -66,6 +68,9 @@ import static org.hamcrest.Matchers.hasSize;
|
||||
|
||||
public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest {
|
||||
|
||||
@Page
|
||||
protected DeviceActivityPage deviceActivityPage;
|
||||
|
||||
@Test
|
||||
public void categoriesTest() {
|
||||
testContext.setTestRealmReps(emptyList()); // reimport realm after this test
|
||||
@@ -77,7 +82,8 @@ public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest {
|
||||
|
||||
// Delete WebAuthn flow ==> Passwordless category should disappear
|
||||
testRealmResource().flows().deleteFlow(WEBAUTHN_FLOW_ID);
|
||||
refreshPageAndWaitForLoad();
|
||||
deviceActivityPage.navigateToUsingSidebar();
|
||||
signingInPage.navigateToUsingSidebar();
|
||||
|
||||
assertThat(signingInPage.getCategoriesCount(), is(2));
|
||||
}
|
||||
@@ -190,8 +196,8 @@ public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest {
|
||||
|
||||
@Test
|
||||
public void setUpLinksTest() {
|
||||
testSetUpLink(testRealmResource(), webAuthnCredentialType, WebAuthnRegisterFactory.PROVIDER_ID);
|
||||
testSetUpLink(testRealmResource(), webAuthnPwdlessCredentialType, WebAuthnPasswordlessRegisterFactory.PROVIDER_ID);
|
||||
testSetUpLink(testRealmResource(), webAuthnCredentialType, WebAuthnRegisterFactory.PROVIDER_ID, deviceActivityPage);
|
||||
testSetUpLink(testRealmResource(), webAuthnPwdlessCredentialType, WebAuthnPasswordlessRegisterFactory.PROVIDER_ID, deviceActivityPage);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -275,7 +281,7 @@ public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest {
|
||||
assertThat(credentialId, notNullValue());
|
||||
testUserResource().removeCredential(credentialId);
|
||||
|
||||
driver.navigate().refresh();
|
||||
refreshPageAndWaitForLoad();
|
||||
|
||||
webAuthnLoginPage.assertCurrent();
|
||||
authenticators = webAuthnLoginPage.getAuthenticators();
|
||||
@@ -478,11 +484,12 @@ public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest {
|
||||
assertThat(credentialType.getUserCredentialsCount(), is(2));
|
||||
assertUserCredential(label2, true, webAuthn2);
|
||||
|
||||
RequiredActionProviderRepresentation requiredAction = new RequiredActionProviderRepresentation();
|
||||
RequiredActionProviderRepresentation requiredAction = testRealmResource().flows().getRequiredAction(providerId);
|
||||
requiredAction.setEnabled(false);
|
||||
testRealmResource().flows().updateRequiredAction(providerId, requiredAction);
|
||||
|
||||
refreshPageAndWaitForLoad();
|
||||
deviceActivityPage.navigateToUsingSidebar();
|
||||
signingInPage.navigateToUsingSidebar();
|
||||
|
||||
assertThat("Set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isSetUpLinkVisible(), is(false));
|
||||
assertThat("Not set up link for \"" + credentialType.getType() + "\" is visible", credentialType.isNotSetUpLabelVisible(), is(false));
|
||||
@@ -490,5 +497,7 @@ public class WebAuthnSigningInTest extends AbstractWebAuthnAccountTest {
|
||||
assertThat(credentialType.getUserCredentialsCount(), is(2));
|
||||
|
||||
testRemoveCredential(webAuthn1);
|
||||
requiredAction.setEnabled(true);
|
||||
testRealmResource().flows().updateRequiredAction(providerId, requiredAction);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user