diff --git a/authentik/stages/authenticator_webauthn/views.py b/authentik/stages/authenticator_webauthn/views.py index 4c3095236..5ff9da611 100644 --- a/authentik/stages/authenticator_webauthn/views.py +++ b/authentik/stages/authenticator_webauthn/views.py @@ -6,11 +6,7 @@ from django.shortcuts import get_object_or_404 from django.views import View from django.views.generic import TemplateView from structlog.stdlib import get_logger -from webauthn import ( - WebAuthnAssertionOptions, - WebAuthnAssertionResponse, - WebAuthnUser, -) +from webauthn import WebAuthnAssertionOptions, WebAuthnAssertionResponse, WebAuthnUser from webauthn.webauthn import ( AuthenticationRejectedException, RegistrationRejectedException, diff --git a/tests/e2e/test_flows_enroll.py b/tests/e2e/test_flows_enroll.py index 76bbf4832..6a76645d0 100644 --- a/tests/e2e/test_flows_enroll.py +++ b/tests/e2e/test_flows_enroll.py @@ -7,6 +7,7 @@ from django.test import override_settings from docker.types import Healthcheck from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as ec +from selenium.webdriver.support.wait import WebDriverWait from authentik.core.models import User from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding @@ -36,6 +37,7 @@ class TestFlowsEnroll(SeleniumTestCase): } @retry() + # pylint: disable=too-many-locals def test_enroll_2_step(self): """Test 2-step enroll flow""" # First stage fields @@ -87,17 +89,44 @@ class TestFlowsEnroll(SeleniumTestCase): FlowStageBinding.objects.create(target=flow, stage=user_login, order=3) self.driver.get(self.live_server_url) - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "#enroll"))) - self.driver.find_element(By.CSS_SELECTOR, "#enroll").click() - self.wait.until(ec.presence_of_element_located((By.ID, "id_username"))) - self.driver.find_element(By.ID, "id_username").send_keys("foo") - self.driver.find_element(By.ID, "id_password").send_keys(USER().username) - self.driver.find_element(By.ID, "id_password_repeat").send_keys(USER().username) - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() - self.driver.find_element(By.ID, "id_name").send_keys("some name") - self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz") - self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + flow_executor = self.get_shadow_root("ak-flow-executor") + identification_stage = self.get_shadow_root( + "ak-stage-identification", flow_executor + ) + wait = WebDriverWait(identification_stage, self.wait_timeout) + + wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "#enroll"))) + identification_stage.find_element(By.CSS_SELECTOR, "#enroll").click() + + flow_executor = self.get_shadow_root("ak-flow-executor") + prompt_stage = self.get_shadow_root("ak-stage-prompt", flow_executor) + wait = WebDriverWait(prompt_stage, self.wait_timeout) + + wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "input[name=username]")) + ) + prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys( + "foo" + ) + prompt_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys( + USER().username + ) + prompt_stage.find_element( + By.CSS_SELECTOR, "input[name=password_repeat]" + ).send_keys(USER().username) + prompt_stage.find_element(By.CSS_SELECTOR, ".pf-c-button").click() + + flow_executor = self.get_shadow_root("ak-flow-executor") + prompt_stage = self.get_shadow_root("ak-stage-prompt", flow_executor) + + prompt_stage.find_element(By.CSS_SELECTOR, "input[name=name]").send_keys( + "some name" + ) + prompt_stage.find_element(By.CSS_SELECTOR, "input[name=email]").send_keys( + "foo@bar.baz" + ) + prompt_stage.find_element(By.CSS_SELECTOR, ".pf-c-button").click() self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar"))) self.driver.get(self.shell_url("authentik_core:user-settings")) diff --git a/tests/e2e/utils.py b/tests/e2e/utils.py index e42fde3bd..e100eb7c5 100644 --- a/tests/e2e/utils.py +++ b/tests/e2e/utils.py @@ -25,6 +25,7 @@ from selenium.common.exceptions import ( from selenium.webdriver.common.by import By from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.remote.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support.ui import WebDriverWait from structlog.stdlib import get_logger @@ -43,14 +44,16 @@ class SeleniumTestCase(StaticLiveServerTestCase): """StaticLiveServerTestCase which automatically creates a Webdriver instance""" container: Optional[Container] = None + wait_timeout: int def setUp(self): super().setUp() + self.wait_timeout = 60 makedirs("selenium_screenshots/", exist_ok=True) self.driver = self._get_driver() self.driver.maximize_window() self.driver.implicitly_wait(30) - self.wait = WebDriverWait(self.driver, 60) + self.wait = WebDriverWait(self.driver, self.wait_timeout) self.apply_default_data() self.logger = get_logger() if specs := self.get_container_specs(): @@ -112,6 +115,18 @@ class SeleniumTestCase(StaticLiveServerTestCase): """same as self.url() but show URL in shell""" return f"{self.live_server_url}/#{reverse(view, kwargs=kwargs)}" + def get_shadow_root( + self, selector: str, container: Optional[WebElement] = None + ) -> WebElement: + """Get shadow root element's inner shadowRoot""" + if not container: + container = self.driver + shadow_root = container.find_element(By.CSS_SELECTOR, selector) + element = self.driver.execute_script( + "return arguments[0].shadowRoot", shadow_root + ) + return element + def assert_user(self, expected_user: User): """Check users/me API and assert it matches expected_user""" self.driver.get(self.url("authentik_api:user-me") + "?format=json")