Skip to content

Conversation

GirlBossRush
Copy link
Contributor

@GirlBossRush GirlBossRush commented Oct 14, 2025

Details

This PR separates styles applied to the document root and body, fixing two issues:

  • Global variables are no longer injected twice that mess with precedence on default values.
  • Global styles which cannot apply in element style roots are no longer parsed and injected

Blocking Dependents

@GirlBossRush GirlBossRush requested review from a team as code owners October 14, 2025 16:46
Copy link

netlify bot commented Oct 14, 2025

Deploy Preview for authentik-storybook ready!

Name Link
🔨 Latest commit ad12165
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/68f108790cd4480008f74327
😎 Deploy Preview https://deploy-preview-17444--authentik-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

netlify bot commented Oct 14, 2025

Deploy Preview for authentik-integrations ready!

Name Link
🔨 Latest commit ad12165
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/68f1087983d230000808834b
😎 Deploy Preview https://deploy-preview-17444--authentik-integrations.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

netlify bot commented Oct 14, 2025

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit ad12165
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/68f108791b41c800084f42cf
😎 Deploy Preview https://deploy-preview-17444--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

codecov bot commented Oct 14, 2025

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
2194 2 2192 2
View the top 2 failed test(s) by shortest run time
authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests::test_stage_context_data_duplicate
Stack Traces | 6.83s run time
self = <unittest.case._Outcome object at 0x7f3ce7cc00c0>
test_case = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.13.7................../x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
result = <TestCaseFunction test_stage_context_data_duplicate>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.13.7................../x64/lib/python3.13/unittest/case.py:651: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
method = <bound method AuthenticatorSMSStageTests.test_stage_context_data_duplicate of <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.13.7................../x64/lib/python3.13/unittest/case.py:606: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>

    def test_stage_context_data_duplicate(self):
        """test stage context data (phone number exists already)"""
        self.client.get(
            reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}),
        )
        plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
        plan.context[PLAN_CONTEXT_PROMPT] = {
            PLAN_CONTEXT_PHONE: "1234",
        }
        session = self.client.session
        session[SESSION_KEY_PLAN] = plan
        session.save()
        SMSDevice.objects.create(
            phone_number="1234",
            user=self.user,
            stage=self.stage,
        )
        sms_send_mock = MagicMock()
        with (
            patch(
                "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send",
                sms_send_mock,
            ),
        ):
            response = self.client.get(
                reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
            )
>       self.assertStageResponse(
            response,
            self.flow,
            self.user,
            component="ak-stage-authenticator-sms",
            phone_number_required=True,
        )

.../stages/authenticator_sms/tests.py:206: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
response = <HttpChallengeResponse status_code=200, "application/json">
flow = <Flow: Flow efLMqwa63G (eflmqwa63g)>, user = <User: OEJae7tZvPN44QI2md2I>
kwargs = {'component': 'ak-stage-authenticator-sms', 'phone_number_required': True}
raw_response = {'component': 'ak-stage-authenticator-sms', 'flow_info': {'background': '.../assets/images/flow_background.jp...avatar.com/avatar/d412d6a4eddeaa0ba9dd1f8b6937593e0619734e125b06a50dfa52a9ddf09372?size=158&rating=g&default=404', ...}

    def assertStageResponse(
        self,
        response: HttpResponse,
        flow: Flow | None = None,
        user: User | None = None,
        **kwargs,
    ) -> dict[str, Any]:
        """Assert various attributes of a stage response"""
        self.assertEqual(response.status_code, 200)
        raw_response = loads(response.content.decode())
        self.assertIsNotNone(raw_response["component"])
        if flow:
            self.assertIn("flow_info", raw_response)
            self.assertEqual(
                raw_response["flow_info"]["cancel_url"], reverse("authentik_flows:cancel")
            )
            # We don't check the flow title since it will most likely go
            # through ChallengeStageView.format_title() so might not match 1:1
            # self.assertEqual(raw_response["flow_info"]["title"], flow.title)
            self.assertIsNotNone(raw_response["flow_info"]["title"])
        if user:
            self.assertEqual(raw_response["pending_user"], user.username)
>           self.assertEqual(raw_response["pending_user_avatar"], user.avatar)

.../flows/tests/__init__.py:44: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
first = 'https://www.gravatar.com/avatar/d412d6a4eddeaa0ba9dd1f8b6937593e0619734e125b06a50dfa52a9ddf09372?size=158&rating=g&default=404'
second = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZ...JtaWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMjgiIGZvbnQtd2VpZ2h0PSI0MDAiIGR5PSIuMWVtIj5PRTwvdGV4dD48L3N2Zz4='
msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.13.7................../x64/lib/python3.13/unittest/case.py:907: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
first = 'https://www.gravatar.com/avatar/d412d6a4eddeaa0ba9dd1f8b6937593e0619734e125b06a50dfa52a9ddf09372?size=158&rating=g&default=404'
second = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZ...JtaWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMjgiIGZvbnQtd2VpZ2h0PSI0MDAiIGR5PSIuMWVtIj5PRTwvdGV4dD48L3N2Zz4='
msg = None

    def assertMultiLineEqual(self, first, second, msg=None):
        """Assert that two multi-line strings are equal."""
        self.assertIsInstance(first, str, "First argument is not a string")
        self.assertIsInstance(second, str, "Second argument is not a string")
    
        if first != second:
            # Don't use difflib if the strings are too long
            if (len(first) > self._diffThreshold or
                len(second) > self._diffThreshold):
                self._baseAssertEqual(first, second, msg)
    
            # Append \n to both strings if either is missing the \n.
            # This allows the final ndiff to show the \n difference. The
            # exception here is if the string is empty, in which case no
            # \n should be added
            first_presplit = first
            second_presplit = second
            if first and second:
                if first[-1] != '\n' or second[-1] != '\n':
                    first_presplit += '\n'
                    second_presplit += '\n'
            elif second and second[-1] != '\n':
                second_presplit += '\n'
            elif first and first[-1] != '\n':
                first_presplit += '\n'
    
            firstlines = first_presplit.splitlines(keepends=True)
            secondlines = second_presplit.splitlines(keepends=True)
    
            # Generate the message and diff, then raise the exception
            standardMsg = '%s != %s' % _common_shorten_repr(first, second)
            diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.13.7................../x64/lib/python3.13/unittest/case.py:1273: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.stages.authenticator_sms.tests.AuthenticatorSMSStageTests testMethod=test_stage_context_data_duplicate>
msg = "'https://www.gravatar.com/avatar/d412d6a4e[81 chars]=404' != 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0[581 chars]Zz4...aWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMjgiIGZvbnQtd2VpZ2h0PSI0MDAiIGR5PSIuMWVtIj5PRTwvdGV4dD48L3N2Zz4=\n"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: 'https://www.gravatar.com/avatar/d412d6a4e[81 chars]=404' != 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0[581 chars]Zz4='
E       - https://www.gravatar.com/avatar/d412d6a4eddeaa0ba9dd1f8b6937593e0619734e125b06a50dfa52a9ddf09372?size=158&rating=g&default=404
E       + data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCA2NCA2NCIgdmVyc2lvbj0iMS4xIj48cmVjdCBmaWxsPSIjYTZjMjk1IiBjeD0iMzIiIGN5PSIzMiIgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByPSIzMiIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBzdHlsZT0iY29sb3I6ICNmZmY7IGxpbmUtaGVpZ2h0OiAxOyBmb250LWZhbWlseTogJ1JlZEhhdFRleHQnLCdPdmVycGFzcycsb3ZlcnBhc3MsaGVsdmV0aWNhLGFyaWFsLHNhbnMtc2VyaWY7ICIgZmlsbD0iI2ZmZiIgYWxpZ25tZW50LWJhc2VsaW5lPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtc2l6ZT0iMjgiIGZvbnQtd2VpZ2h0PSI0MDAiIGR5PSIuMWVtIj5PRTwvdGV4dD48L3N2Zz4=

.../hostedtoolcache/Python/3.13.7................../x64/lib/python3.13/unittest/case.py:732: AssertionError
tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2::test_oauth_link
Stack Traces | 232s run time
self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
>           return func(self, *args, **kwargs)

tests/e2e/utils.py:324: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>,)
kwargs = {}, file = 'default/flow-default-invalidation-flow.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Invalidation flow\nentries:\n- attrs:\n    designation: invalidation\n    na...0\n    stage: !KeyOf default-invalidation-logout\n    target: !KeyOf flow\n  model: authentik_flows.flowstagebinding\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>,)
kwargs = {}, file = 'default/flow-default-source-pre-authentication.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Source pre-authentication flow\nentries:\n- attrs:\n    designation: stage_c...    authentication: none\n  identifiers:\n    slug: default-source-pre-authentication\n  model: authentik_flows.flow\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-source-authentication.yaml",
        "default/flow-default-source-enrollment.yaml",
        "default/flow-default-source-pre-authentication.yaml",
    )
    def test_oauth_link(self):
        """test OAuth Source link OIDC"""
        self.create_objects()
        self.driver.get(self.live_server_url)
        self.login()
    
        self.driver.get(
            self.url("authentik_sources_oauth:oauth-client-login", source_slug=self.slug)
        )
    
        # Now we should be at the IDP, wait for the login field
>       self.wait.until(ec.presence_of_element_located((By.ID, "login")))

tests/e2e/test_source_oauth_oauth2.py:181: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <selenium.webdriver.support.wait.WebDriverWait (session="5b41d20195c306385f0ebde9810bb0ed")>
method = <function presence_of_element_located.<locals>._predicate at 0x7f6d34a162a0>
message = ''

    def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = "") -> T:
        """Wait until the method returns a value that is not False.
    
        Calls the method provided with the driver as an argument until the
        return value does not evaluate to ``False``.
    
        Parameters:
        -----------
        method: callable(WebDriver)
            - A callable object that takes a WebDriver instance as an argument.
    
        message: str
            - Optional message for :exc:`TimeoutException`
    
        Return:
        -------
        object: T
            - The result of the last call to `method`
    
        Raises:
        -------
        TimeoutException
            - If 'method' does not return a truthy value within the WebDriverWait
            object's timeout
    
        Example:
        --------
        >>> from selenium.webdriver.common.by import By
        >>> from selenium.webdriver.support.ui import WebDriverWait
        >>> from selenium.webdriver.support import expected_conditions as EC
    
        # Wait until an element is visible on the page
        >>> wait = WebDriverWait(driver, 10)
        >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId")))
        >>> print(element.text)
        """
        screen = None
        stacktrace = None
    
        end_time = time.monotonic() + self._timeout
        while True:
            try:
                value = method(self._driver)
                if value:
                    return value
            except self._ignored_exceptions as exc:
                screen = getattr(exc, "screen", None)
                stacktrace = getattr(exc, "stacktrace", None)
            if time.monotonic() > end_time:
                break
            time.sleep(self._poll)
>       raise TimeoutException(message, screen, stacktrace)
E       selenium.common.exceptions.TimeoutException: Message: 
E       Stacktrace:
E       #0 0x556249f47362 <unknown>
E       #1 0x5562499b755b <unknown>
E       #2 0x556249a080bb <unknown>
E       #3 0x556249a08295 <unknown>
E       #4 0x556249a512c8 <unknown>
E       #5 0x556249a2cdc1 <unknown>
E       #6 0x556249a4ed36 <unknown>
E       #7 0x556249a2cb47 <unknown>
E       #8 0x5562499fa861 <unknown>
E       #9 0x5562499fb645 <unknown>
E       #10 0x556249f112de <unknown>
E       #11 0x556249f1473f <unknown>
E       #12 0x556249f141dc <unknown>
E       #13 0x556249f14be9 <unknown>
E       #14 0x556249efacbb <unknown>
E       #15 0x556249f14f74 <unknown>
E       #16 0x556249ee454d <unknown>
E       #17 0x556249f33ff9 <unknown>
E       #18 0x556249f341ef <unknown>
E       #19 0x556249f45d19 <unknown>
E       #20 0x7fe6477d7aa4 <unknown>
E       #21 0x7fe647864a64 __clone

.venv/lib/python3.13.../webdriver/support/wait.py:146: TimeoutException

During handling of the above exception, another exception occurred:

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
>           return func(self, *args, **kwargs)

tests/e2e/utils.py:324: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>,)
kwargs = {}, file = 'default/flow-default-invalidation-flow.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Invalidation flow\nentries:\n- attrs:\n    designation: invalidation\n    na...0\n    stage: !KeyOf default-invalidation-logout\n    target: !KeyOf flow\n  model: authentik_flows.flowstagebinding\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>,)
kwargs = {}, file = 'default/flow-default-source-pre-authentication.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Source pre-authentication flow\nentries:\n- attrs:\n    designation: stage_c...    authentication: none\n  identifiers:\n    slug: default-source-pre-authentication\n  model: authentik_flows.flow\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-source-authentication.yaml",
        "default/flow-default-source-enrollment.yaml",
        "default/flow-default-source-pre-authentication.yaml",
    )
    def test_oauth_link(self):
        """test OAuth Source link OIDC"""
        self.create_objects()
        self.driver.get(self.live_server_url)
        self.login()
    
        self.driver.get(
            self.url("authentik_sources_oauth:oauth-client-login", source_slug=self.slug)
        )
    
        # Now we should be at the IDP, wait for the login field
>       self.wait.until(ec.presence_of_element_located((By.ID, "login")))

tests/e2e/test_source_oauth_oauth2.py:181: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <selenium.webdriver.support.wait.WebDriverWait (session="d29786815bbdddaac5b4adcdcf316ab0")>
method = <function presence_of_element_located.<locals>._predicate at 0x7f6d34a43e20>
message = ''

    def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = "") -> T:
        """Wait until the method returns a value that is not False.
    
        Calls the method provided with the driver as an argument until the
        return value does not evaluate to ``False``.
    
        Parameters:
        -----------
        method: callable(WebDriver)
            - A callable object that takes a WebDriver instance as an argument.
    
        message: str
            - Optional message for :exc:`TimeoutException`
    
        Return:
        -------
        object: T
            - The result of the last call to `method`
    
        Raises:
        -------
        TimeoutException
            - If 'method' does not return a truthy value within the WebDriverWait
            object's timeout
    
        Example:
        --------
        >>> from selenium.webdriver.common.by import By
        >>> from selenium.webdriver.support.ui import WebDriverWait
        >>> from selenium.webdriver.support import expected_conditions as EC
    
        # Wait until an element is visible on the page
        >>> wait = WebDriverWait(driver, 10)
        >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId")))
        >>> print(element.text)
        """
        screen = None
        stacktrace = None
    
        end_time = time.monotonic() + self._timeout
        while True:
            try:
                value = method(self._driver)
                if value:
                    return value
            except self._ignored_exceptions as exc:
                screen = getattr(exc, "screen", None)
                stacktrace = getattr(exc, "stacktrace", None)
            if time.monotonic() > end_time:
                break
            time.sleep(self._poll)
>       raise TimeoutException(message, screen, stacktrace)
E       selenium.common.exceptions.TimeoutException: Message: 
E       Stacktrace:
E       #0 0x55acde41e362 <unknown>
E       #1 0x55acdde8e55b <unknown>
E       #2 0x55acddedf0bb <unknown>
E       #3 0x55acddedf295 <unknown>
E       #4 0x55acddf282c8 <unknown>
E       #5 0x55acddf03dc1 <unknown>
E       #6 0x55acddf25d36 <unknown>
E       #7 0x55acddf03b47 <unknown>
E       #8 0x55acdded1861 <unknown>
E       #9 0x55acdded2645 <unknown>
E       #10 0x55acde3e82de <unknown>
E       #11 0x55acde3eb73f <unknown>
E       #12 0x55acde3eb1dc <unknown>
E       #13 0x55acde3ebbe9 <unknown>
E       #14 0x55acde3d1cbb <unknown>
E       #15 0x55acde3ebf74 <unknown>
E       #16 0x55acde3bb54d <unknown>
E       #17 0x55acde40aff9 <unknown>
E       #18 0x55acde40b1ef <unknown>
E       #19 0x55acde41cd19 <unknown>
E       #20 0x7f317ed44aa4 <unknown>
E       #21 0x7f317edd1a64 __clone

.venv/lib/python3.13.../webdriver/support/wait.py:146: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f6d38727020>
test_case = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
result = <TestCaseFunction test_oauth_link>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:651: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
method = <bound method TestSourceOAuth2.test_oauth_link of <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:606: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
            return func(self, *args, **kwargs)
    
        except tuple(exceptions) as exc:
            count += 1
            if count > max_retires:
                logger.debug("Exceeded retry count", exc=exc, test=self)
    
                raise exc
            logger.debug("Retrying on error", exc=exc, test=self)
            self.tearDown()
            self._post_teardown()
            self._pre_setup()
            self.setUp()
>           return wrapper(self, *args, **kwargs)

tests/e2e/utils.py:337: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
            return func(self, *args, **kwargs)
    
        except tuple(exceptions) as exc:
            count += 1
            if count > max_retires:
                logger.debug("Exceeded retry count", exc=exc, test=self)
    
                raise exc
            logger.debug("Retrying on error", exc=exc, test=self)
            self.tearDown()
            self._post_teardown()
            self._pre_setup()
            self.setUp()
>           return wrapper(self, *args, **kwargs)

tests/e2e/utils.py:337: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
            return func(self, *args, **kwargs)
    
        except tuple(exceptions) as exc:
            count += 1
            if count > max_retires:
                logger.debug("Exceeded retry count", exc=exc, test=self)
    
>               raise exc

tests/e2e/utils.py:331: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
>           return func(self, *args, **kwargs)

tests/e2e/utils.py:324: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>,)
kwargs = {}, file = 'default/flow-default-invalidation-flow.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Invalidation flow\nentries:\n- attrs:\n    designation: invalidation\n    na...0\n    stage: !KeyOf default-invalidation-logout\n    target: !KeyOf flow\n  model: authentik_flows.flowstagebinding\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>,)
kwargs = {}, file = 'default/flow-default-source-pre-authentication.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Source pre-authentication flow\nentries:\n- attrs:\n    designation: stage_c...    authentication: none\n  identifiers:\n    slug: default-source-pre-authentication\n  model: authentik_flows.flow\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_source_oauth_oauth2.TestSourceOAuth2 testMethod=test_oauth_link>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-source-authentication.yaml",
        "default/flow-default-source-enrollment.yaml",
        "default/flow-default-source-pre-authentication.yaml",
    )
    def test_oauth_link(self):
        """test OAuth Source link OIDC"""
        self.create_objects()
        self.driver.get(self.live_server_url)
        self.login()
    
        self.driver.get(
            self.url("authentik_sources_oauth:oauth-client-login", source_slug=self.slug)
        )
    
        # Now we should be at the IDP, wait for the login field
>       self.wait.until(ec.presence_of_element_located((By.ID, "login")))

tests/e2e/test_source_oauth_oauth2.py:181: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <selenium.webdriver.support.wait.WebDriverWait (session="998e705b87f5a5f7863656c1e36c1796")>
method = <function presence_of_element_located.<locals>._predicate at 0x7f6d34a42980>
message = ''

    def until(self, method: Callable[[D], Union[Literal[False], T]], message: str = "") -> T:
        """Wait until the method returns a value that is not False.
    
        Calls the method provided with the driver as an argument until the
        return value does not evaluate to ``False``.
    
        Parameters:
        -----------
        method: callable(WebDriver)
            - A callable object that takes a WebDriver instance as an argument.
    
        message: str
            - Optional message for :exc:`TimeoutException`
    
        Return:
        -------
        object: T
            - The result of the last call to `method`
    
        Raises:
        -------
        TimeoutException
            - If 'method' does not return a truthy value within the WebDriverWait
            object's timeout
    
        Example:
        --------
        >>> from selenium.webdriver.common.by import By
        >>> from selenium.webdriver.support.ui import WebDriverWait
        >>> from selenium.webdriver.support import expected_conditions as EC
    
        # Wait until an element is visible on the page
        >>> wait = WebDriverWait(driver, 10)
        >>> element = wait.until(EC.visibility_of_element_located((By.ID, "exampleId")))
        >>> print(element.text)
        """
        screen = None
        stacktrace = None
    
        end_time = time.monotonic() + self._timeout
        while True:
            try:
                value = method(self._driver)
                if value:
                    return value
            except self._ignored_exceptions as exc:
                screen = getattr(exc, "screen", None)
                stacktrace = getattr(exc, "stacktrace", None)
            if time.monotonic() > end_time:
                break
            time.sleep(self._poll)
>       raise TimeoutException(message, screen, stacktrace)
E       selenium.common.exceptions.TimeoutException: Message: 
E       Stacktrace:
E       #0 0x55a548a69362 <unknown>
E       #1 0x55a5484d955b <unknown>
E       #2 0x55a54852a0bb <unknown>
E       #3 0x55a54852a295 <unknown>
E       #4 0x55a5485732c8 <unknown>
E       #5 0x55a54854edc1 <unknown>
E       #6 0x55a548570d36 <unknown>
E       #7 0x55a54854eb47 <unknown>
E       #8 0x55a54851c861 <unknown>
E       #9 0x55a54851d645 <unknown>
E       #10 0x55a548a332de <unknown>
E       #11 0x55a548a3673f <unknown>
E       #12 0x55a548a361dc <unknown>
E       #13 0x55a548a36be9 <unknown>
E       #14 0x55a548a1ccbb <unknown>
E       #15 0x55a548a36f74 <unknown>
E       #16 0x55a548a0654d <unknown>
E       #17 0x55a548a55ff9 <unknown>
E       #18 0x55a548a561ef <unknown>
E       #19 0x55a548a67d19 <unknown>
E       #20 0x7f6e9b3daaa4 <unknown>
E       #21 0x7f6e9b467a64 __clone

.venv/lib/python3.13.../webdriver/support/wait.py:146: TimeoutException

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@GirlBossRush GirlBossRush force-pushed the fix-global-style-duplication branch from e9cfdaf to fd12b7d Compare October 14, 2025 17:00
Copy link
Contributor

github-actions bot commented Oct 14, 2025

authentik PR Installation instructions

Instructions for docker-compose

Add the following block to your .env file:

AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-fd12b7d3fe13c6dcb0f9e39897354897928b8c3d
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s

Afterwards, run the upgrade commands from the latest release notes.

Instructions for Kubernetes

Add the following block to your values.yml file:

authentik:
    outposts:
        container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
    image:
        repository: ghcr.io/goauthentik/dev-server
        tag: gh-fd12b7d3fe13c6dcb0f9e39897354897928b8c3d

Afterwards, run the upgrade commands from the latest release notes.

@GirlBossRush GirlBossRush added the area:frontend Features or issues related to the browser, TypeScript, Node.js, etc label Oct 15, 2025
@GirlBossRush GirlBossRush force-pushed the fix-global-style-duplication branch from fd12b7d to ad12165 Compare October 16, 2025 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:frontend Features or issues related to the browser, TypeScript, Node.js, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants