Skip to content

Web/flow/expose flow in lightdom#22103

Open
kensternberg-authentik wants to merge 34 commits intoweb/element/new-drawerfrom
web/flow/expose-flow-in-lightdom
Open

Web/flow/expose flow in lightdom#22103
kensternberg-authentik wants to merge 34 commits intoweb/element/new-drawerfrom
web/flow/expose-flow-in-lightdom

Conversation

@kensternberg-authentik
Copy link
Copy Markdown
Contributor

@kensternberg-authentik kensternberg-authentik commented May 6, 2026

Put the Login input sequence into the lightDOM.

What

  1. Separate the Flow and FlowExecutor into two different components; the Flow is concerned only with the layout of the page and the look and feel, and it hosts the FlowExecutor; the FlowExecutor, in turn, contains only the logic about managing the Flow and its stages.

    Because the FlowExecutor gets some information about the Flow’s look and feel as part of its payload, it communicates those parts (and only those parts) with the Flow via an event. This is a tighter coupling that I might have liked, but it serves our purposes.

  2. Introduces the light() directive, a powerful directive that takes a Lit TemplateResult in-line in a component’s overall template rendering process and replaces the TemplateResult with a slot element, then places the TemplateResult into the component’s lightDOM, thus allowing us to both grant easy traversal to those elements and dictate the location and behavior of those elements.

    One trade-off to this design is that the CSS for those elements that will be in the lightDOM must also be in the parent element’s adoptedStylesheets; since we’re trying to open up the “whole tree of accessible nodes,” that means the document as a whole. We could, in theory, inject those automatically, but the risks of duplication and the violation of the Web Component idiom doesn’t justify doing so at this time.

  3. Wrap the chain of render calls from Flow ⮕ FlowExecutor ⮕ IdentificationStage in the light() directive so that now all of those elements are in the lightDOM. Other stages have not been transformed this way. I am unaware of any that need updating at this time.

Note

This commit is dependent upon the P5 Drawer with all capabilities review. By itself, this commit does not update the Patternfly styling, but we expect to in the near future.

  • [🐉] The code has been formatted (make web)

         window.authentik.flow = {
             "layout": "{{ flow.layout }}",
    +        "background": "{{ flow.background }}",
    +        "title": "{{ flow.title }}",
         };

Amends the `flow.html` template and `GlobalAuthentik` parser to include new parameters, `background` and `title`, in the flow-specific part of the configuration written to the HTML `<head>` object, and to provide those parameters to client code.

## Why

The `layout` is start-up critical: it tells the Flow interface how the admin wants the Flow page to look, and allows the HTML and CSS to be pre-aligned to that condition. `layout` is determined on a per-Flow bases, not a per-Stage basis; Flows are derived from a tuple of `(Brand, Application?)`, where the opening policy *may* direct a user to a different flow if the user reached authentik via a redirect from a specific application, but will otherwise fall back to the default Flow for the Brand.

The `background` is a field that is required if the `Flow`’s layout is of type `frame_background`; in this case, the part of the viewport not dedicated to the FlowExecutor is reserved for an `<iframe>` that will be filled in with whatever the administrator specifies. Although this gives it the same priority as `layout` (whether it’s provided or undefined) for describing the [chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome) around a challenge, it is currently not provided to the application in the start-up config; it is provided in the `challenge` and renders the IFrame as part of the initial challenge.

This patch fixes that; if `layout` is provided, `background` ought to be as well, even if it’s empty. The execution of a Challenge ought not have any influence over the look and feel of the Flow-defined appearance *around* that Challenge.

I have added `title` as well; with that, all of the current theme-and-appearance related configuration details are placed into `<head>` and can be removed from the FlowExecutor.

Server-side, `background` is currently specified: `background = FileField(blank=True, default="")` which is … interesting since we also appear to store URLs in it. I don’t see anything in the FlowSerializer that would change that from a client’s point of view.

This patch furthers the effort to separate flow execution from flow presentation.

- \[🐰\] The code has been formatted (`make web`)
* main: (36 commits)
  website: fix typos (#20996)
  internal/outpost/ak: fix ws URL on outpost restart (#21041)
  sources/ldap: fix incorrect error response for invalid sync_users_password (#21016)
  website/docs: add missing dependencies for linux dev environment (#21020)
  core, web: update translations (#21021)
  web: bump flatted from 3.4.1 to 3.4.2 in /web (#21037)
  web: bump @sentry/browser from 10.44.0 to 10.45.0 in /web in the sentry group across 1 directory (#21022)
  website: bump flatted from 3.4.1 to 3.4.2 in /website (#21038)
  core: bump astral-sh/uv from 0.10.11 to 0.10.12 in /lifecycle/container (#21027)
  ci: bump actions-rust-lang/setup-rust-toolchain from 1.15.3 to 1.15.4 in /.github/actions/setup (#21030)
  ci: bump taiki-e/install-action from 2.68.26 to 2.69.2 in /.github/actions/setup (#21029)
  core: bump goauthentik/fips-debian from `7baeeaa` to `7726387` in /lifecycle/container (#21028)
  core: bump aws-cdk-lib from 2.243.0 to 2.244.0 (#21026)
  core: bump types-ldap3 from 2.9.13.20251121 to 2.9.13.20260319 (#21024)
  core: bump ruff from 0.15.6 to 0.15.7 (#21023)
  core: bump goauthentik/fips-python from `859ad57` to `bf45eb7` in /lifecycle/container (#21025)
  website/integrations: fix AWS SCIM with Identity Center (#21017)
  root: allow listening on multiple IPs (#20930)
  website: switch docs analytics to gtag (#20993)
  web: link file picker to docs (#20995)
  ...
* main: (22 commits)
  ci: rotate GH App private key (#21085)
  internal/web: remove authentication for metrics (#21077)
  lib/config: explicit some defaults (#21079)
  internal: remove unix sockets on shutdown (#21081)
  ci: fix escaping in cherry-pick action (#21082)
  lib/config: support printing multiple values (#21080)
  root: fix rust setup (#21078)
  core: bump types-docker from 7.1.0.20260109 to 7.1.0.20260322 (#21062)
  policies: remove BufferedPolicyAccessView leftovers (#21057)
  core: bump axllent/mailpit from v1.29.3 to v1.29.4 in /tests/e2e (#21061)
  core: bump types-channels from 4.3.0.20250822 to 4.3.0.20260321 (#21063)
  core: bump github.com/jackc/pgx/v5 from 5.8.0 to 5.9.1 (#21059)
  translate: Updates for project authentik and language fr_FR (#21056)
  ci: bump taiki-e/install-action from 2.69.2 to 2.69.6 in /.github/actions/setup (#21068)
  web: bump the storybook group across 1 directory with 5 updates (#21031)
  web: bump knip from 5.88.0 to 5.88.1 in /web (#21033)
  web: bump type-fest from 5.4.4 to 5.5.0 in /web (#21032)
  events: prevent exception when events contains incompatible unicode (#21048)
  web/admin: handle non-string values in formatUUID to prevent Event Log crash (#20804)
  events: avoid implicitly setting context from login_failed event (#21045)
  ...
* main:
  core: remove filter_not_expired for QS (#18274)
  tenants: fix default schema in initial migration (#21114)
  core: bump django-stubs[compatible-mypy] from 5.2.9 to 6.0.1 (#21099)
  core, web: update translations (#21097)
  lifecycle/aws: bump aws-cdk from 2.1112.0 to 2.1113.0 in /lifecycle/aws (#21098)
  core: bump types-requests from 2.32.4.20260107 to 2.32.4.20260324 (#21100)
  core: bump constructs from 10.5.1 to 10.6.0 (#21101)
  core: bump astral-sh/uv from 0.10.12 to 0.11.0 in /lifecycle/container (#21103)
  ci: bump taiki-e/install-action from 2.69.6 to 2.69.7 in /.github/actions/setup (#21104)
  web: bump flatted from 3.4.1 to 3.4.2 (#21076)
  core: bump goauthentik.io/api/v3 to 3.2026.5.0-rc1-1774286095 (#21089)
  core: bump cbor2 from 5.8.0 to 5.9.0 (#21094)
  ci: fix cherry-pick action generating empty title (#21091)
  web: bump the swc group across 1 directory with 11 updates (#21070)
  web: bump yaml from 2.8.2 to 2.8.3 in /web (#21071)
  core: add flag for future default behaviour of requiring a binding to access an application (#16247)
* main: (26 commits)
  endpoints/connectors: fix enabled flag not respected (#21144)
  web: bump vite from 7.3.1 to 8.0.2 in /web (#21109)
  website/docs: add a single page about our user interface, document Consent stage (#20533)
  website: bump the build group across 1 directory with 9 updates (#21127)
  web: bump knip from 5.88.1 to 6.0.5 in /web (#21129)
  core: bump drf-spectacular from 0.28.0 to 0.29.0 (#19420)
  packages/client-go: init (#21139)
  providers/proxy: Add a default maxResponseBodySize to Traefik Middleware (#21111)
  core: bump library/nginx from `dec7a90` to `7150b3a` in /website (#21137)
  core: bump gunicorn from 25.1.0 to 25.2.0 (#21134)
  core: bump github.com/getsentry/sentry-go from 0.43.0 to 0.44.1 (#21122)
  core: bump astral-sh/uv from 0.11.0 to 0.11.1 in /lifecycle/container (#21135)
  ci: bump taiki-e/install-action from 2.69.8 to 2.69.9 in /.github/actions/setup (#21136)
  web/a11y: Modals, Command Palette (Merge branch) (#17812)
  website/docs: document file picker values (#20994)
  packages/client-rust: init (#21117)
  core: bump sentry-sdk from 2.55.0 to 2.56.0 (#21124)
  events: add helper to log deprecation configuration_warning message (#21115)
  core: bump djangorestframework from 3.17.0 to 3.17.1 (#21126)
  core: bump twilio from 9.10.3 to 9.10.4 (#21123)
  ...
* main: (21 commits)
  root: cleanup API generation (#21172)
  packages/client-ts: init (#21120)
  core, web: update translations (#21159)
  website: bump @goauthentik/docusaurus-config from 2.5.1 to 2.6.0 in /website in the docusaurus group (#21161)
  core: bump cryptography from 46.0.5 to 46.0.6 (#21162)
  core: bump library/node from 25.8.1-trixie to 25.8.2-trixie in /website (#21163)
  ci: bump taiki-e/install-action from 2.69.9 to 2.69.10 in /.github/actions/setup (#21164)
  web: bump the goauthentik group across 1 directory with 3 updates (#21165)
  web: bump typescript from 5.9.3 to 6.0.2 in /web (#21107)
  web/flows: fix continuous flow leftovers (#21158)
  web: bump picomatch from 4.0.3 to 4.0.4 (#21157)
  web: bump yaml from 2.8.2 to 2.8.3 (#21156)
  website: bump picomatch in /website (#21155)
  web: bump smol-toml from 1.6.0 to 1.6.1 (#21154)
  web: bump picomatch from 2.3.1 to 2.3.2 in /web (#21153)
  web: bump smol-toml from 1.6.0 to 1.6.1 in /web (#21152)
  root: optimise api client generation speed (#21141)
  website/integrations: nextcloud add back-channel logout documentation (#21147)
  core: bump requests from 2.32.5 to 2.33.0 (#21146)
  web: bump chromedriver from 146.0.5 to 146.0.6 in /web (#21128)
  ...
* main:
  website/integrations: add OAUTH_AUTO_REDIRECT for karakeep (#21180)
  website/integrations: beszel: add email scope (#21176)
  web: lint/small type errors (#21179)
  packages/django-dramatiq-postgres: add index for (queue_name, state, eta) (#21175)
  root: add git attributes for generated/vendored (#21177)
  web: bump vite from 8.0.2 to 8.0.3 in /web (#21171)
  core, web: Vendored client follow-ups (#21174)
  website: Enable gtag in production (#21151)
* main: (52 commits)
  stages/authenticator_webauthn: save attestation certificate when creating credential (#20095)
  web/admin: fix missing icon on app view page (#21251)
  web/elements: allow table per-column options (#21250)
  ci: bump actions/setup-go from 6.3.0 to 6.4.0 (#21245)
  web: bump knip from 6.0.6 to 6.1.0 in /web (#21241)
  web: bump globby from 16.1.1 to 16.2.0 in /web (#21242)
  core: bump types-requests from 2.32.4.20260324 to 2.33.0.20260327 (#21236)
  core: bump types-docker from 7.1.0.20260322 to 7.1.0.20260328 (#21237)
  core: bump aws-cdk-lib from 2.244.0 to 2.245.0 (#21238)
  ci: bump int128/docker-manifest-create-action from 2.16.0 to 2.17.0 (#21244)
  ci: bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in /.github/actions/setup (#21246)
  ci: bump taiki-e/install-action from 2.69.12 to 2.70.2 in /.github/actions/setup (#21247)
  ci: bump actions/setup-go from 6.3.0 to 6.4.0 in /.github/actions/setup (#21248)
  core, web: update translations (#21233)
  translate: Updates for project authentik and language fr_FR (#21214)
  web/admin: polish recent events, various button alignments and labels (#21232)
  outposts: Create separate metrics service in Kubernetes (#21229)
  events: fix exception in volume endpoint, adjust simple table size (#21230)
  core: Application stats, device events & cleanup (#21225)
  core: bump axllent/mailpit from v1.29.4 to v1.29.5 in /tests/e2e (#21226)
  ...
* main:
  translate: Updates for project authentik and language fr_FR (#21285)
  packages/django-postgres-cache: rework to use ORM (#17771)
  providers/saml: Fix redirect for saml slo (#21258)
  core: fix provider not nullable (#21275)
  website/docs: ad source: add note about ldap signing (#21274)
  website/api: update API clients doc (#21202)
  ci: bump taiki-e/install-action from 2.70.2 to 2.70.3 in /.github/actions/setup (#21267)
  lifecycle/aws: bump aws-cdk from 2.1114.1 to 2.1115.0 in /lifecycle/aws (#21265)
  core, web: update translations (#21264)
  packages/ak-lib: init (#21257)
  website/docs: document group_uuid as a property for group object (#20865)
  web/flow: extract lifecycle events peripheral to stage management into their own controllers (#20898)
  core: bump pygments from 2.19.2 to 2.20.0 (#21260)
  website/docs: add grafana dashboard (#21254)
* main:
  packages/django-postgres-cache: fix expiry and delete (#21307)
  website/docs: entra scim: add note about validator (#21273)
  core: bump djangorestframework-stubs[compatible-mypy] from 3.16.8 to 3.16.9 (#21294)
  web: bump @xmldom/xmldom from 0.8.11 to 0.8.12 in /web (#21301)
  blueprints: rework one-time import (#18074)
  lifecycle/aws: bump aws-cdk from 2.1115.0 to 2.1115.1 in /lifecycle/aws (#21293)
  core, web: update translations (#21288)
  core: bump sentry-sdk from 2.56.0 to 2.57.0 (#21295)
  core: bump aws-cdk-lib from 2.245.0 to 2.246.0 (#21296)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#21290)
  ci: bump getsentry/action-release from 3.5.0 to 3.6.0 (#21298)
  ci: bump taiki-e/install-action from 2.70.3 to 2.70.4 in /.github/actions/setup (#21299)
  website/docs: add example recovery flow with MFA (#19497)
  website/docs: format cache settings (#21289)
  source/saml: Add forceauthn to saml authnrequest (#20883)
* main: (26 commits)
  root: fix compose generation for patch releases release candidates (#21353)
  web: bump @swc/cli from 0.8.0 to 0.8.1 in /web in the swc group across 1 directory (#21300)
  providers/proxy: fix oidc client not using socket in embedded outpost (#21280)
  packages/client-rust: fix portable sed usage (#21337)
  packages/ak-common/tokio/proxy_procotol: init (#21311)
  packages/ak-common/config: init (#21256)
  core: bump beryju.io/ldap from 0.1.0 to 0.2.1 (#21235)
  web: bump @sentry/browser from 10.46.0 to 10.47.0 in /web in the sentry group across 1 directory (#21297)
  packages/ak-common/arbiter: init (#21253)
  website/docs: fix full dev setup ordering (#21332)
  core: bump types-docker from 7.1.0.20260328 to 7.1.0.20260402 (#21342)
  packages/ak-common: rename from ak-lib (#21314)
  root: fix rustfmt config (#21312)
  core: bump types-ldap3 from 2.9.13.20260319 to 2.9.13.20260402 (#21343)
  web: bump the bundler group across 1 directory with 4 updates (#21345)
  core: bump aiohttp from 3.13.3 to 3.13.4 (#21333)
  core, web: update translations (#21335)
  lifecycle/aws: bump aws-cdk from 2.1115.1 to 2.1116.0 in /lifecycle/aws (#21338)
  core: bump types-requests from 2.33.0.20260327 to 2.33.0.20260402 (#21339)
  core: bump django-stubs[compatible-mypy] from 6.0.1 to 6.0.2 (#21340)
  ...
* main:
  website/docs: Clean up PostgreSQL documentation (#21131)
* main: (58 commits)
  packages/ak-axum/error: init (#21315)
  packages/ak-axum: init (#21313)
  website: bump the build group across 1 directory with 9 updates (#21396)
  core: bump jwcrypto from 1.5.6 to 1.5.7 (#21423)
  web: bump fuse.js from 7.1.0 to 7.3.0 in /web (#21429)
  web: bump the bundler group across 1 directory with 3 updates (#21425)
  web: bump cspell from 9.7.0 to 10.0.0 (#21427)
  web: bump knip from 6.1.0 to 6.3.0 in /web (#21428)
  sources/ldap: Switch to new connection tracking, deprecated attribute-based connection (#21392)
  packages/ak-common/mode: init (#21259)
  packages/ak-common/tracing: init (#21263)
  web/admin: Improve WS-Fed algo selection logic (#20881)
  packages/ak-common/tls: init (#21262)
  packages/ak-common/config: add set helper for tests (#21356)
  tasks: allow retry for rejected tasks only (#21433)
  core, web: update translations (#21394)
  website/docs: clarify file upload troubleshooting (#21361)
  ci: bump aws-actions/configure-aws-credentials from 6.0.0 to 6.1.0 (#21424)
  core: bump uvicorn[standard] from 0.43.0 to 0.44.0 (#21422)
  ci: bump taiki-e/install-action from 2.73.0 to 2.74.0 in /.github/actions/setup (#21426)
  ...
* main: (38 commits)
  website/integrations: update FortiGate SSLVPN doc (#21475)
  ci: cache apt install (#21480)
  packages/ak-common: use imports where possible (#21478)
  packages/ak-axum/server: cleanup unix socket (#21477)
  packages/ak-common, ak-axum: improve logging (#21476)
  packages/ak-axum/extract/scheme: init (#21322)
  core: fix policy binding objects not being nullable (#21421)
  packages/ak-axum/extract/client_ip: init (#21321)
  translate: Updates for project authentik and language fr_FR (#21474)
  website: bump the docusaurus group in /website with 10 updates (#21452)
  packages/docusaurus-config: update config for docusaurus 3.10 (#21471)
  packages/ak-axum/extract/trusted_proxy: init (#21320)
  web: Fix duplicate Turnstile widgets after extended idle (#21380)
  packages/ak-axum/accept/proxy_protocol: init (#21319)
  web: bump chromedriver from 147.0.0 to 147.0.1 in /web (#21467)
  ci: fix `docker-push-variables` (#21470)
  core, web: update translations (#21450)
  docs,ci: fix main daily compose downloads + release template (#21448)
  web: bump the storybook group across 1 directory with 5 updates (#21460)
  core: bump cryptography from 46.0.6 to 46.0.7 (#21456)
  ...
* main: (36 commits)
  web/e2e: accept options in NavigatorFixture.waitForPathname (#21507)
  web/styles: switch to upstream RedHat variable fonts and brighten orange palette (#21509)
  web/styles: add ak-c-loading-skeleton CSS component (#21510)
  core, web: update translations (#21532)
  core: bump lxml from 6.0.2 to 6.0.3 (#21523)
  core: bump library/node from `45babd1` to `9707cd4` in /lifecycle/container (#21522)
  tasks: better error message for Retry exceptions (#18235)
  web/admin: fix user list avatar (#21531)
  core: bump django from v5.2.12 to 5.2.13 (#21520)
  core: add cooldown to dependabot (#21286)
  web/admin: include avatar in user list page (#21518)
  events: add index on Event.user.pk (#19576)
  ci: always run apt update (#21516)
  enterprise/search: move QL to open source] (#21484)
  core: add logging when session decode fails (#21514)
  website/docs: Refactor email configuration (#21130)
  core: bump types-ldap3 from 2.9.13.20260402 to 2.9.13.20260408 (#21493)
  packages/ak-common/db: init (#21357)
  packages/ak-axum/extract/host: init (#21323)
  web: bump knip from 6.3.0 to 6.3.1 in /web (#21505)
  ...
* main: (269 commits)
  root: fix rust build with uv-installed Python (#21858)
  core: add support for hiding applications from the user dashboard (#21530)
  core: bump ruff from 0.15.11 to 0.15.12 (#21871)
  packages/ak-axum/router: add X-Powered-By to all responses (#21940)
  core: bump microsoft-kiota-serialization-form from 1.9.8 to v1.10.1 (#21909)
  core: bump pytest-randomly from 4.0.1 to 4.1.0 (#21873)
  core: users/groups reduce number of database queries (#20431)
  core: bump types-channels from 4.3.0.20260408 to 4.3.0.20260421 (#21872)
  ci: bump taiki-e/install-action from 2.75.21 to 2.75.22 in /.github/actions/setup (#21877)
  core, web: update translations (#21870)
  sources/oauth: ensure user ID is returned as str (#21880)
  translate: Updates for project authentik and language no_NO (#21862)
  core: bump maxminddb from 3.0.0 to v3.1.1 (#21907)
  core: bump prometheus-client from 0.24.0 to v0.25.0 (#21919)
  core: bump azure-identity from 1.25.1 to v1.25.3 (#21886)
  core: bump aiohttp from 3.13.4 to v3.13.5 (#21882)
  core: bump anyio from 4.12.1 to v4.13.0 (#21883)
  core: bump asgiref from 3.11.0 to v3.11.1 (#21884)
  core: bump azure-core from 1.38.0 to v1.39.0 (#21885)
  core: bump blessed from 1.25.0 to v1.38.0 (#21887)
  ...
* main:
  web: bump knip from 6.6.0 to 6.6.3 in /web (#21981)
  packages/ak-common/tracing: make log level lowercase (#21991)
  root: only allow listen failure in dev (#21987)
  flows: preserve signed background URLs in CSS (#21868)
  core, web: update translations (#21966)
  core: fix search for app entitlements failing (#21944)
  ci: bump taiki-e/install-action from 2.75.22 to 2.75.23 in /.github/actions/setup (#21982)
  website/integrations: Refactor and cleanup GitHub Enterprise (#21685)
  web: Clear remember me before navigation. (#21647)
  web: bump knip from 6.4.1 to 6.6.0 in /web (#21957)
  core: bump github.com/getsentry/sentry-go from 0.45.1 to 0.46.0 (#21955)
  core: bump uvicorn[standard] from 0.44.0 to 0.45.0 (#21956)
  core: bump rustls from 0.23.39 to 0.23.40 (#21958)
  core: support hashed password in users API + automated install (#18686)
  core, web: update translations (#21952)
  providers/saml: generate issuer url when provider is set on app (#18022)
* main:
  web/admin: use bindings form for app entitlements (#22007)
  website/integrations: Add guide to integrate Technitium DNS with authentik (#21826)
  website/docs: clarify M2M scope requests (#21977)
  website/docs: clarify LDAP TLS verification (#21974)
  website/docs: clarify blueprint identifiers (#21976)
  website/docs: document promoted sources (#21979)
  lifecycle/aws: bump aws-cdk from 2.1118.4 to 2.1119.0 in /lifecycle/aws (#22001)
  web: bump the swc group across 1 directory with 11 updates (#22004)
  core: bump uvicorn[standard] from 0.45.0 to 0.46.0 (#22002)
  web: bump @sentry/browser from 10.49.0 to 10.50.0 in /web in the sentry group across 1 directory (#22003)
  ci: bump taiki-e/install-action from 2.75.23 to 2.75.25 in /.github/actions/setup (#22005)
  core: bump reqwest from 0.13.2 to 0.13.3 (#22006)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#21999)
  core, web: update translations (#21998)
  enterprise: account lockdown (#18615)
  enterprise/lifecycle: remove one review per object limitation (#21046)
* main: (36 commits)
  website/integrations: actual budget: add env var  (#22036)
  website/docs: add webhook mapping examples (#21971)
  website/integrations: fix nextcloud LDAP group mapping (#21970)
  website/docs: preserve blueprint download filenames (#21969)
  web: Radio and Checkbox Input Revisions (#21792)
  providers/oauth: make rp init logout oidc certification changes (#21815)
  website/docs: document SCIM custom attributes (#21980)
  root: introduce allinone mode (#21990)
  website/docs: add SAML source mapping guidance (#21978)
  website/docs: fix misplaced AWS-LC clang warning (#22034)
  lifecycle/worker_process: fix healthchecks and metrics not reloading db connections after a failure (#21992)
  root: add more logging to worker requests (#21989)
  root/channels: use group_send_blocking where possible (#21993)
  core, web: update translations (#22014)
  translate: Updates for project authentik and language fr_FR (#22015)
  enterprise/providers/ssf: more conformance fixes (#21521)
  web/flows: update flow background (#22032)
  website/integrations: update NetBox OIDC config (#22018)
  web: bump @formatjs/intl-listformat from 8.3.2 to 8.3.4 in /web (#22026)
  web: bump knip from 6.6.3 to 6.7.0 in /web (#22027)
  ...
* main: (24 commits)
  root: update django to 5.2.14 (#22064)
  tenants: add option to mark flag as deprecated (#22063)
  web/stages: better wording for webauthn authenticator attachments options (#22062)
  web: bump vite from 8.0.8 to 8.0.10 in /web (#21842)
  api: set authenticated session user agent nullable properties (#22059)
  web/admin: redirect stage: adds mention of static url (#22060)
  web: bump axios from 1.15.0 to 1.16.0 in /web (#22058)
  providers/oauth2: override RedirectURITypeEnum capitalization for generated API (#22037)
  website/docs: document language settings (#21968)
  website/docs: document supported PostgreSQL versions (#21967)
  website: bump docusaurus-theme-openapi-docs from 5.0.1 to 5.0.2 in /website (#22052)
  web: bump the storybook group across 1 directory with 5 updates (#22024)
  revert: web: Consistent use of "User Dashboard" (#22038) (#22046)
  core: bump metrics-exporter-prometheus from 0.18.1 to 0.18.3 (#22057)
  core, web: update translations (#22047)
  core: bump cryptography from 47.0.0 to 48.0.0 (#22053)
  core: bump psycopg[c,pool] from 3.3.3 to 3.3.4 (#22054)
  ci: bump taiki-e/install-action from 2.75.28 to 2.75.29 in /.github/actions/setup (#22056)
  web: remove native fieldset borders from action groups (#21334)
  website/docs: document blueprint import options (#21973)
  ...
* main:
  root: ensure uv sync does not update uv.lock (#22084)
  core: bump dramatiq from 1.17.1 to 2.1.0 (#22076)
  web: Fix Vendored Lex package. Add Unit Tests  (#22083)
  core, web: update translations (#22074)
  website: bump the build group in /website with 6 updates (#22075)
  web: bump ip-address from 10.1.0 to 10.2.0 in /web (#22082)
  web: bump the swc group across 1 directory with 11 updates (#22078)
  ci: bump taiki-e/install-action from 2.75.29 to 2.75.30 in /.github/actions/setup (#22077)
  web: bump country-flag-icons from 1.6.16 to 1.6.17 in /web (#22079)
  web: bump yaml from 2.8.3 to 2.8.4 in /web (#22080)
  core: bump sentry from 0.47.0 to 0.48.0 (#22081)
  packages/client-ts: Fix TypeScript config, ESBuild warnings (#21863)
  web: fix identification stage OUIA attributes (#22049)
  stages/invitation: Invitation wizard (#20399)
  Web/release202604/nits 2 (#22040)
  web: Gracefully handle missing element construction. (#21787)
…ghtdom

* web/element/new-drawer:
  Prettier has opinions, as usual.
  Extend ak-drawer to comply with the full specification; port ak-drawer to use Patternfly 5; vendor the Patternfly 5 subsystems directly responsible for the Drawer into the CSS.
  Nobody liked this choice.
  Re-arranged to avoid having a 'devDependencies' block.
  TSC (!) had opinions.
  ## What
  That was a stupid spelling error.
  Did I miss something?
  .
We now have *three* different copies of the Login.css: One for Flow, one for FlowExecutor, and one used by all the stages. Their use cases have almost nothing to do with one another; they represent isolated and independent uses of CSS that we will eventually have to put into separate files of their own.
@kensternberg-authentik kensternberg-authentik changed the base branch from main to web/element/new-drawer May 6, 2026 22:43
@netlify
Copy link
Copy Markdown

netlify Bot commented May 6, 2026

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit a3e9788
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69fbc321fc7cb700082cd647
😎 Deploy Preview https://deploy-preview-22103--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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

❌ 14 Tests Failed:

Tests completed Failed Passed Skipped
1417 14 1403 1
View the top 3 failed test(s) by shortest run time
tests.e2e.test_provider_ws_fed.TestProviderWSFed::test_sp_initiated_explicit
Stack Traces | 43.4s run time
self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="0aa4862a0880710a54c305f2edd964fb", element="f.2B3DD74FF261A676C5B4A71A050C253B.d.B1D744BF5E4E821F29335D1FBC34EB71.e.7")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f5901743410>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f58fa48fe20>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55fb9645b302 <unknown>
E       #1 0x55fb95e300c6 <unknown>
E       #2 0x55fb95e8225c <unknown>
E       #3 0x55fb95e824a5 <unknown>
E       #4 0x55fb95e7661a <unknown>
E       #5 0x55fb95ea7861 <unknown>
E       #6 0x55fb95e764e2 <unknown>
E       #7 0x55fb95ea7ba2 <unknown>
E       #8 0x55fb95ec9d7d <unknown>
E       #9 0x55fb95ea75d7 <unknown>
E       #10 0x55fb95e748b2 <unknown>
E       #11 0x55fb95e75725 <unknown>
E       #12 0x55fb9641ed44 <unknown>
E       #13 0x55fb96422086 <unknown>
E       #14 0x55fb96421b3e <unknown>
E       #15 0x55fb964224f9 <unknown>
E       #16 0x55fb9640e6fa <unknown>
E       #17 0x55fb9642287a <unknown>
E       #18 0x55fb963f6e49 <unknown>
E       #19 0x55fb96447b79 <unknown>
E       #20 0x55fb96447d6d <unknown>
E       #21 0x55fb96459903 <unknown>
E       #22 0x7f7330739469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f58fb528b90>
test_case = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
result = <TestCaseFunction test_sp_initiated_explicit>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
method = <bound method TestProviderWSFed.test_sp_initiated_explicit of <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>,)
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_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>,)
kwargs = {}
file = 'default/flow-default-provider-authorization-explicit-consent.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Provider authorization flow (explicit consent)\nentries:\n- attrs:\n    desi...e: !KeyOf default-provider-authorization-consent\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_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>,)
kwargs = {}, file = 'system/providers-saml.yaml'
content = 'version: 1\nmetadata:\n  labels:\n    blueprints.goauthentik.io/system: "true"\n  name: System - SAML Provider - Mapp...rosoft..../identity/claims/windowsaccountname"\n      expression: |\n        return request.user.username\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_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>,)
kwargs = {}, config = <AuthentikCryptoConfig: authentik_crypto>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^

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

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-provider-authorization-explicit-consent.yaml",
    )
    @apply_blueprint(
        "system/providers-saml.yaml",
    )
    @reconcile_app("authentik_crypto")
    def test_sp_initiated_explicit(self):
        """test WSFed Provider flow SP-initiated flow (explicit consent)"""
        # Bootstrap all needed objects
        authorization_flow = Flow.objects.get(
            slug="default-provider-authorization-explicit-consent"
        )
        provider = WSFederationProvider.objects.create(
            name=generate_id(),
            acs_url="http://localhost:8080",
            audience=self.realm,
            authorization_flow=authorization_flow,
            signing_kp=create_test_cert(),
        )
        provider.property_mappings.set(SAMLPropertyMapping.objects.all())
        provider.save()
        app = Application.objects.create(
            name="WSFed",
            slug=generate_id(),
            provider=provider,
        )
        self.setup_client(provider, app)
        self.driver.get("http://localhost:8080")
>       self.login()

tests/e2e/test_provider_ws_fed.py:169: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="0aa4862a0880710a54c305f2edd964fb", element="f.2B3DD74FF261A676C5B4A71A050C253B.d.B1D744BF5E4E821F29335D1FBC34EB71.e.7")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_explicit>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_proxy_forward.TestProviderProxyForward::test_envoy
Stack Traces | 47s run time
self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="707ca408ef716e3db8f1c6c978caa2a4", element="f.D32250457DB672A11D6BE06D0C267D99.d.7BE42949382C7F4AA0DCC57ED2BE90CC.e.7")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f231045d490>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f230d256f00>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55ede2ade302 <unknown>
E       #1 0x55ede24b30c6 <unknown>
E       #2 0x55ede250525c <unknown>
E       #3 0x55ede25054a5 <unknown>
E       #4 0x55ede24f961a <unknown>
E       #5 0x55ede252a861 <unknown>
E       #6 0x55ede24f94e2 <unknown>
E       #7 0x55ede252aba2 <unknown>
E       #8 0x55ede254cd7d <unknown>
E       #9 0x55ede252a5d7 <unknown>
E       #10 0x55ede24f78b2 <unknown>
E       #11 0x55ede24f8725 <unknown>
E       #12 0x55ede2aa1d44 <unknown>
E       #13 0x55ede2aa5086 <unknown>
E       #14 0x55ede2aa4b3e <unknown>
E       #15 0x55ede2aa54f9 <unknown>
E       #16 0x55ede2a916fa <unknown>
E       #17 0x55ede2aa587a <unknown>
E       #18 0x55ede2a79e49 <unknown>
E       #19 0x55ede2acab79 <unknown>
E       #20 0x55ede2acad6d <unknown>
E       #21 0x55ede2adc903 <unknown>
E       #22 0x7fee6c724469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f2312b30b90>
test_case = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
result = <TestCaseFunction test_envoy>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
method = <bound method TestProviderProxyForward.test_envoy of <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>

    @retry()
    def test_envoy(self):
        """Test envoy"""
        self.run_container(
            image="docker.io/envoyproxy/envoy:v1.25-latest",
            ports={
                "10000": "80",
            },
            volumes={
                f"{Path(__file__).parent / "proxy_forward_auth" / "envoy_single" / "envoy.yaml"}": {
                    "bind": ".../etc/envoy/envoy.yaml",
                }
            },
        )
    
        self.prepare()
    
        self.driver.get("http:.../localhost/api")
>       self.login()

tests/e2e/test_provider_proxy_forward.py:180: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="707ca408ef716e3db8f1c6c978caa2a4", element="f.D32250457DB672A11D6BE06D0C267D99.d.7BE42949382C7F4AA0DCC57ED2BE90CC.e.7")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_envoy>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_proxy_forward.TestProviderProxyForward::test_caddy
Stack Traces | 47.2s run time
self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="928821cea38819a59544db9677078c4f", element="f.611ECACCD09863EF1498491D9FCC936A.d.7C5B837A9F4A5BC15DE3553AF9962D79.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f2320a3f250>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f2320b53a00>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55b9d25f7302 <unknown>
E       #1 0x55b9d1fcc0c6 <unknown>
E       #2 0x55b9d201e25c <unknown>
E       #3 0x55b9d201e4a5 <unknown>
E       #4 0x55b9d201261a <unknown>
E       #5 0x55b9d2043861 <unknown>
E       #6 0x55b9d20124e2 <unknown>
E       #7 0x55b9d2043ba2 <unknown>
E       #8 0x55b9d2065d7d <unknown>
E       #9 0x55b9d20435d7 <unknown>
E       #10 0x55b9d20108b2 <unknown>
E       #11 0x55b9d2011725 <unknown>
E       #12 0x55b9d25bad44 <unknown>
E       #13 0x55b9d25be086 <unknown>
E       #14 0x55b9d25bdb3e <unknown>
E       #15 0x55b9d25be4f9 <unknown>
E       #16 0x55b9d25aa6fa <unknown>
E       #17 0x55b9d25be87a <unknown>
E       #18 0x55b9d2592e49 <unknown>
E       #19 0x55b9d25e3b79 <unknown>
E       #20 0x55b9d25e3d6d <unknown>
E       #21 0x55b9d25f5903 <unknown>
E       #22 0x7fdf2a3d1469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f2312b30e10>
test_case = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
result = <TestCaseFunction test_caddy>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
method = <bound method TestProviderProxyForward.test_caddy of <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>

    @retry()
    def test_caddy(self):
        """Test caddy"""
        local_config_path = (
            Path(__file__).parent / "proxy_forward_auth" / "caddy_single" / "Caddyfile"
        )
        self.run_container(
            image="docker.io/library/caddy:2.8",
            ports={
                "80": "80",
            },
            volumes={
                local_config_path: {
                    "bind": ".../etc/caddy/Caddyfile",
                }
            },
        )
    
        self.prepare()
    
        self.driver.get("http:.../localhost/api")
>       self.login()

tests/e2e/test_provider_proxy_forward.py:221: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="928821cea38819a59544db9677078c4f", element="f.611ECACCD09863EF1498491D9FCC936A.d.7C5B837A9F4A5BC15DE3553AF9962D79.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_caddy>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_proxy.TestProviderProxy::test_proxy_simple
Stack Traces | 48s run time
self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="4ab5b4f31af9ebcd185b7eecc26aebc3", element="f.F5FA9EBF6228F2CBAB7D02D1D4CEACFF.d.8B798AF68CADF240035A958066844D98.e.7")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f23081eb4d0>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f2320957530>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x564c27ad9302 <unknown>
E       #1 0x564c274ae0c6 <unknown>
E       #2 0x564c2750025c <unknown>
E       #3 0x564c275004a5 <unknown>
E       #4 0x564c274f461a <unknown>
E       #5 0x564c27525861 <unknown>
E       #6 0x564c274f44e2 <unknown>
E       #7 0x564c27525ba2 <unknown>
E       #8 0x564c27547d7d <unknown>
E       #9 0x564c275255d7 <unknown>
E       #10 0x564c274f28b2 <unknown>
E       #11 0x564c274f3725 <unknown>
E       #12 0x564c27a9cd44 <unknown>
E       #13 0x564c27aa0086 <unknown>
E       #14 0x564c27a9fb3e <unknown>
E       #15 0x564c27aa04f9 <unknown>
E       #16 0x564c27a8c6fa <unknown>
E       #17 0x564c27aa087a <unknown>
E       #18 0x564c27a74e49 <unknown>
E       #19 0x564c27ac5b79 <unknown>
E       #20 0x564c27ac5d6d <unknown>
E       #21 0x564c27ad7903 <unknown>
E       #22 0x7f110d5c2469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f23208d9ba0>
test_case = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
result = <TestCaseFunction test_proxy_simple>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
method = <bound method TestProviderProxy.test_proxy_simple of <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>,)
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_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>,)
kwargs = {}, file = 'default/flow-default-provider-invalidation.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Provider invalidation flow\nentries:\n- attrs:\n    designation: invalidatio...ation: none\n  identifiers:\n    slug: default-provider-invalidation-flow\n  model: authentik_flows.flow\n  id: 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: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>,)
kwargs = {}, file = 'system/providers-proxy.yaml'
content = 'version: 1\nmetadata:\n  labels:\n    blueprints.goauthentik.io/system: "true"\n  name: System - Proxy Provider - Sco...ser.group_attributes(request),\n                "is_superuser": request.user.is_superuser,\n            }\n        }\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_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>,)
kwargs = {}, config = <AuthentikCryptoConfig: authentik_crypto>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^

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

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-provider-authorization-implicit-consent.yaml",
        "default/flow-default-provider-invalidation.yaml",
    )
    @apply_blueprint(
        "system/providers-oauth2.yaml",
        "system/providers-proxy.yaml",
    )
    @reconcile_app("authentik_crypto")
    def test_proxy_simple(self):
        """Test simple outpost setup with single provider"""
        # set additionalHeaders to test later
        self.user.attributes["additionalHeaders"] = {"X-Foo": "bar"}
        self.user.save()
    
        proxy: ProxyProvider = ProxyProvider.objects.create(
            name=generate_id(),
            authorization_flow=Flow.objects.get(
                slug="default-provider-authorization-implicit-consent"
            ),
            invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
            internal_host=f"http://{self.host}",
            external_host="http://localhost:9000",
        )
        # Ensure OAuth2 Params are set
        proxy.set_oauth_defaults()
        proxy.save()
        # we need to create an application to actually access the proxy
        Application.objects.create(name=generate_id(), slug=generate_id(), provider=proxy)
        outpost: Outpost = Outpost.objects.create(
            name=generate_id(),
            type=OutpostType.PROXY,
        )
        outpost.providers.add(proxy)
        outpost.build_user_permissions(outpost.user)
    
        self.start_proxy(outpost)
    
        sleep(5)
    
        self.driver.get("http://localhost:9000/api")
>       self.login()

tests/e2e/test_provider_proxy.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="4ab5b4f31af9ebcd185b7eecc26aebc3", element="f.F5FA9EBF6228F2CBAB7D02D1D4CEACFF.d.8B798AF68CADF240035A958066844D98.e.7")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_simple>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_proxy.TestProviderProxy::test_proxy_basic_auth
Stack Traces | 48.8s run time
self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="8ccea2fe57df4ca21f6daa4e280330cb", element="f.A2DF18A48232E1CD18CC24DD7BFAB907.d.C1441F13076B536365BE2619463EAADD.e.10")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f22f4660600>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f22f45054e0>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55dfab247302 <unknown>
E       #1 0x55dfaac1c0c6 <unknown>
E       #2 0x55dfaac6e25c <unknown>
E       #3 0x55dfaac6e4a5 <unknown>
E       #4 0x55dfaac6261a <unknown>
E       #5 0x55dfaac93861 <unknown>
E       #6 0x55dfaac624e2 <unknown>
E       #7 0x55dfaac93ba2 <unknown>
E       #8 0x55dfaacb5d7d <unknown>
E       #9 0x55dfaac935d7 <unknown>
E       #10 0x55dfaac608b2 <unknown>
E       #11 0x55dfaac61725 <unknown>
E       #12 0x55dfab20ad44 <unknown>
E       #13 0x55dfab20e086 <unknown>
E       #14 0x55dfab20db3e <unknown>
E       #15 0x55dfab20e4f9 <unknown>
E       #16 0x55dfab1fa6fa <unknown>
E       #17 0x55dfab20e87a <unknown>
E       #18 0x55dfab1e2e49 <unknown>
E       #19 0x55dfab233b79 <unknown>
E       #20 0x55dfab233d6d <unknown>
E       #21 0x55dfab245903 <unknown>
E       #22 0x7fe2effc5469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f230f9aed50>
test_case = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
result = <TestCaseFunction test_proxy_basic_auth>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
method = <bound method TestProviderProxy.test_proxy_basic_auth of <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>,)
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_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>,)
kwargs = {}, file = 'default/flow-default-provider-invalidation.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Provider invalidation flow\nentries:\n- attrs:\n    designation: invalidatio...ation: none\n  identifiers:\n    slug: default-provider-invalidation-flow\n  model: authentik_flows.flow\n  id: 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: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>,)
kwargs = {}, file = 'system/providers-proxy.yaml'
content = 'version: 1\nmetadata:\n  labels:\n    blueprints.goauthentik.io/system: "true"\n  name: System - Proxy Provider - Sco...ser.group_attributes(request),\n                "is_superuser": request.user.is_superuser,\n            }\n        }\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_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>,)
kwargs = {}, config = <AuthentikCryptoConfig: authentik_crypto>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^

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

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-provider-authorization-implicit-consent.yaml",
        "default/flow-default-provider-invalidation.yaml",
    )
    @apply_blueprint(
        "system/providers-oauth2.yaml",
        "system/providers-proxy.yaml",
    )
    @reconcile_app("authentik_crypto")
    def test_proxy_basic_auth(self):
        """Test simple outpost setup with single provider"""
        cred = generate_id()
        attr = "basic-password"  # nosec
        self.user.attributes["basic-username"] = cred
        self.user.attributes[attr] = cred
        self.user.save()
    
        proxy: ProxyProvider = ProxyProvider.objects.create(
            name=generate_id(),
            authorization_flow=Flow.objects.get(
                slug="default-provider-authorization-implicit-consent"
            ),
            invalidation_flow=Flow.objects.get(slug="default-provider-invalidation-flow"),
            internal_host=f"http://{self.host}",
            external_host="http://localhost:9000",
            basic_auth_enabled=True,
            basic_auth_user_attribute="basic-username",
            basic_auth_password_attribute=attr,
        )
        # Ensure OAuth2 Params are set
        proxy.set_oauth_defaults()
        proxy.save()
        # we need to create an application to actually access the proxy
        Application.objects.create(name=generate_id(), slug=generate_id(), provider=proxy)
        outpost: Outpost = Outpost.objects.create(
            name=generate_id(),
            type=OutpostType.PROXY,
        )
        outpost.providers.add(proxy)
        outpost.build_user_permissions(outpost.user)
    
        self.start_proxy(outpost)
    
        sleep(5)
    
        self.driver.get("http://localhost:9000/api")
>       self.login()

tests/e2e/test_provider_proxy.py:180: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="8ccea2fe57df4ca21f6daa4e280330cb", element="f.A2DF18A48232E1CD18CC24DD7BFAB907.d.C1441F13076B536365BE2619463EAADD.e.10")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy.TestProviderProxy testMethod=test_proxy_basic_auth>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic::test_oidcc_basic_certification_test
Stack Traces | 108s run time
self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="0fa66cc9cefb47becef593274df8c299", element="f.D90DF12934C8B551F7DCBCB72A745AD4.d.003CE99A36B6CDDE727772990EF24F89.e.8")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f7635edf610>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f7628f6c720>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55f56bb69302 <unknown>
E       #1 0x55f56b53e0c6 <unknown>
E       #2 0x55f56b59025c <unknown>
E       #3 0x55f56b5904a5 <unknown>
E       #4 0x55f56b58461a <unknown>
E       #5 0x55f56b5b5861 <unknown>
E       #6 0x55f56b5844e2 <unknown>
E       #7 0x55f56b5b5ba2 <unknown>
E       #8 0x55f56b5d7d7d <unknown>
E       #9 0x55f56b5b55d7 <unknown>
E       #10 0x55f56b5828b2 <unknown>
E       #11 0x55f56b583725 <unknown>
E       #12 0x55f56bb2cd44 <unknown>
E       #13 0x55f56bb30086 <unknown>
E       #14 0x55f56bb2fb3e <unknown>
E       #15 0x55f56bb304f9 <unknown>
E       #16 0x55f56bb1c6fa <unknown>
E       #17 0x55f56bb3087a <unknown>
E       #18 0x55f56bb04e49 <unknown>
E       #19 0x55f56bb55b79 <unknown>
E       #20 0x55f56bb55d6d <unknown>
E       #21 0x55f56bb67903 <unknown>
E       #22 0x7f78dbb47469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f76373ddbe0>
test_case = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
result = <TestCaseFunction test_oidcc_basic_certification_test>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
method = <bound method TestOpenIDConformanceBasic.test_oidcc_basic_certification_test of <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>

    @retry()
    def test_oidcc_basic_certification_test(self):
>       self.run_test(
            "oidcc-basic-certification-test-plan",
            self.test_plan_config,
            {
                "server_metadata": "discovery",
                "client_registration": "static_client",
            },
        )

tests/openid_conformance/test_oidc_basic.py:9: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
test_name = 'oidcc-basic-certification-test-plan'
test_plan_config = {'alias': 'authentik', 'client': {'client_id': '4054d882aff59755f2f279968b97ce8806a926e1', 'client_secret': '4c7e49330...86b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867'}, ...}
test_variant = {'client_registration': 'static_client', 'server_metadata': 'discovery'}

    def run_test(
        self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
    ):
        self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
    
        test_plan = self.conformance.create_test_plan(
            test_name,
            test_plan_config,
            test_variant,
        )
        plan_id = test_plan["id"]
        for test in test_plan["modules"]:
            # Fetch name and variant of the next test to run
            module_name = test["testModule"]
            variant = test["variant"]
            module_instance = self.conformance.create_test_from_plan_with_variant(
                plan_id, module_name, variant
            )
            module_id = module_instance["id"]
>           self.run_single_test(module_id)

tests/openid_conformance/base.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
module_id = 'mBgKJvbU7CkxfUQ'

    def run_single_test(self, module_id: str):
        """Process instructions for a single test, navigate to browser URLs and take screenshots"""
        tested_browser_url = 0
        uploaded_image = 0
        cleared_cookies = False
        while True:
            # Fetch all info
            test_status = self.conformance.get_test_status(module_id)
            test_log = self.conformance.get_test_log(module_id)
            test_info = self.conformance.get_module_info(module_id)
            # Check status early, if we're finished already we don't want to do anything extra
            if test_info["status"] in ["INTERRUPTED", "FINISHED"]:
                return
            # Check if we need to clear cookies - tests only indicates this in their written summary
            # so this check is a bit brittle
            if "cookies" in test_info["summary"] and not cleared_cookies:
                # Navigate to our origin to delete cookies in the right context
                self.driver.get(self.url("authentik_api:user-me") + "?format=json")
                self.driver.delete_all_cookies()
                cleared_cookies = True
            # Check if we need deal with any browser URLs
            browser_urls = test_status.get("browser", {}).get("urls", [])
            if len(browser_urls) > tested_browser_url:
>               self.do_browser(browser_urls[tested_browser_url])

tests/openid_conformance/base.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
url = 'https://10.1.0.212:.../application/o/authorize/?client_id=4054d882aff59755f2f279968b97ce8806a926e1&nonce=lnynnjVu1o&redirect_uri=https://localhost:.../a/authentik/callback&response_type=code&scope=openid&state=CZSxcvlivp'

    def do_browser(self, url):
        """For any specific OpenID Conformance test, execute the operations required"""
        self.driver.get(url)
        should_expect_completion = False
        if "if/flow/default-authentication-flow" in self.driver.current_url:
            self.logger.debug("Logging in")
            skipped = []
            if "login_hint" in self.driver.current_url:
                skipped.append("ak-stage-identification")
>           self.login(skip_stages=skipped)

tests/openid_conformance/base.py:143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="0fa66cc9cefb47becef593274df8c299", element="f.D90DF12934C8B551F7DCBCB72A745AD4.d.003CE99A36B6CDDE727772990EF24F89.e.8")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_basic.TestOpenIDConformanceBasic testMethod=test_oidcc_basic_certification_test>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel::test_oidcc_frontchannel_logout_certification_test_plan
Stack Traces | 108s run time
self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="9a11c5db4d1810a833122bed80ef8f5e", element="f.59DD29E8BBF50E2D6668DE1C04A5DA9E.d.87E711090149EFB833C25F8B3BA16360.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f97aab6ec10>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f97a86bd430>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x555fc9bf5302 <unknown>
E       #1 0x555fc95ca0c6 <unknown>
E       #2 0x555fc961c25c <unknown>
E       #3 0x555fc961c4a5 <unknown>
E       #4 0x555fc961061a <unknown>
E       #5 0x555fc9641861 <unknown>
E       #6 0x555fc96104e2 <unknown>
E       #7 0x555fc9641ba2 <unknown>
E       #8 0x555fc9663d7d <unknown>
E       #9 0x555fc96415d7 <unknown>
E       #10 0x555fc960e8b2 <unknown>
E       #11 0x555fc960f725 <unknown>
E       #12 0x555fc9bb8d44 <unknown>
E       #13 0x555fc9bbc086 <unknown>
E       #14 0x555fc9bbbb3e <unknown>
E       #15 0x555fc9bbc4f9 <unknown>
E       #16 0x555fc9ba86fa <unknown>
E       #17 0x555fc9bbc87a <unknown>
E       #18 0x555fc9b90e49 <unknown>
E       #19 0x555fc9be1b79 <unknown>
E       #20 0x555fc9be1d6d <unknown>
E       #21 0x555fc9bf3903 <unknown>
E       #22 0x7fdd749fb469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f97abd53380>
test_case = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
result = <TestCaseFunction test_oidcc_frontchannel_logout_certification_test_plan>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
method = <bound method TestOpenIDConformanceFrontchannel.test_oidcc_frontchannel_logout_certification_test_plan of <tests.openi...oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>

    @retry()
    def test_oidcc_frontchannel_logout_certification_test_plan(self):
>       self.run_test(
            "oidcc-frontchannel-rp-initiated-logout-certification-test-plan",
            self.test_plan_config,
            {
                "client_registration": "static_client",
                "response_type": "code",
            },
        )

tests/openid_conformance/test_oidc_frontchannel.py:19: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
test_name = 'oidcc-frontchannel-rp-initiated-logout-certification-test-plan'
test_plan_config = {'alias': 'authentik', 'client': {'client_id': '4054d882aff59755f2f279968b97ce8806a926e1', 'client_secret': '4c7e49330...86b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867'}, ...}
test_variant = {'client_registration': 'static_client', 'response_type': 'code'}

    def run_test(
        self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
    ):
        self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
    
        test_plan = self.conformance.create_test_plan(
            test_name,
            test_plan_config,
            test_variant,
        )
        plan_id = test_plan["id"]
        for test in test_plan["modules"]:
            # Fetch name and variant of the next test to run
            module_name = test["testModule"]
            variant = test["variant"]
            module_instance = self.conformance.create_test_from_plan_with_variant(
                plan_id, module_name, variant
            )
            module_id = module_instance["id"]
>           self.run_single_test(module_id)

tests/openid_conformance/base.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
module_id = 'IZBQ26ohJIRwhrt'

    def run_single_test(self, module_id: str):
        """Process instructions for a single test, navigate to browser URLs and take screenshots"""
        tested_browser_url = 0
        uploaded_image = 0
        cleared_cookies = False
        while True:
            # Fetch all info
            test_status = self.conformance.get_test_status(module_id)
            test_log = self.conformance.get_test_log(module_id)
            test_info = self.conformance.get_module_info(module_id)
            # Check status early, if we're finished already we don't want to do anything extra
            if test_info["status"] in ["INTERRUPTED", "FINISHED"]:
                return
            # Check if we need to clear cookies - tests only indicates this in their written summary
            # so this check is a bit brittle
            if "cookies" in test_info["summary"] and not cleared_cookies:
                # Navigate to our origin to delete cookies in the right context
                self.driver.get(self.url("authentik_api:user-me") + "?format=json")
                self.driver.delete_all_cookies()
                cleared_cookies = True
            # Check if we need deal with any browser URLs
            browser_urls = test_status.get("browser", {}).get("urls", [])
            if len(browser_urls) > tested_browser_url:
>               self.do_browser(browser_urls[tested_browser_url])

tests/openid_conformance/base.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
url = 'https://10.1.0.152:.../application/o/authorize/?client_id=4054d882aff59755f2f279968b97ce8806a926e1&nonce=zw3HtZVg5l...N4Xd7tqSueTB6fpjzrBPTQTsH4kbkuca74XUa3uTM5BsqO7tpdQ7VIkspagNCYxXwcnTpX55bgGBSutbcgdw7yJYpkI0YgCSnKtZT6BmHB7Cpyl8k2-._~'

    def do_browser(self, url):
        """For any specific OpenID Conformance test, execute the operations required"""
        self.driver.get(url)
        should_expect_completion = False
        if "if/flow/default-authentication-flow" in self.driver.current_url:
            self.logger.debug("Logging in")
            skipped = []
            if "login_hint" in self.driver.current_url:
                skipped.append("ak-stage-identification")
>           self.login(skip_stages=skipped)

tests/openid_conformance/base.py:143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="9a11c5db4d1810a833122bed80ef8f5e", element="f.59DD29E8BBF50E2D6668DE1C04A5DA9E.d.87E711090149EFB833C25F8B3BA16360.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_frontchannel.TestOpenIDConformanceFrontchannel testMethod=test_oidcc_frontchannel_logout_certification_test_plan>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_proxy_forward.TestProviderProxyForward::test_traefik
Stack Traces | 109s run time
self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="57094e8f4bc7a3d93516093c1378919d", element="f.694BAC9DEF85236FFF30B21B8E662E73.d.D29B7FF452E34577641A3B43D07A27E2.e.8")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f2311fc7d90>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f230dcbc720>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x56392f2d8302 <unknown>
E       #1 0x56392ecad0c6 <unknown>
E       #2 0x56392ecff25c <unknown>
E       #3 0x56392ecff4a5 <unknown>
E       #4 0x56392ecf361a <unknown>
E       #5 0x56392ed24861 <unknown>
E       #6 0x56392ecf34e2 <unknown>
E       #7 0x56392ed24ba2 <unknown>
E       #8 0x56392ed46d7d <unknown>
E       #9 0x56392ed245d7 <unknown>
E       #10 0x56392ecf18b2 <unknown>
E       #11 0x56392ecf2725 <unknown>
E       #12 0x56392f29bd44 <unknown>
E       #13 0x56392f29f086 <unknown>
E       #14 0x56392f29eb3e <unknown>
E       #15 0x56392f29f4f9 <unknown>
E       #16 0x56392f28b6fa <unknown>
E       #17 0x56392f29f87a <unknown>
E       #18 0x56392f273e49 <unknown>
E       #19 0x56392f2c4b79 <unknown>
E       #20 0x56392f2c4d6d <unknown>
E       #21 0x56392f2d6903 <unknown>
E       #22 0x7f94eb099469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f231346f620>
test_case = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
result = <TestCaseFunction test_traefik>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
method = <bound method TestProviderProxyForward.test_traefik of <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>

    @retry()
    def test_traefik(self):
        """Test traefik"""
        local_config_path = (
            Path(__file__).parent / "proxy_forward_auth" / "traefik_single" / "config-static.yaml"
        )
        self.run_container(
            image="docker.io/library/traefik:3.1",
            ports={
                "80": "80",
            },
            volumes={
                local_config_path: {
                    "bind": ".../etc/traefik/traefik.yml",
                }
            },
        )
    
        self.prepare()
    
        self.driver.get("http:.../localhost/api")
>       self.login()

tests/e2e/test_provider_proxy_forward.py:102: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="57094e8f4bc7a3d93516093c1378919d", element="f.694BAC9DEF85236FFF30B21B8E662E73.d.D29B7FF452E34577641A3B43D07A27E2.e.8")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_proxy_forward.TestProviderProxyForward testMethod=test_traefik>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_ws_fed.TestProviderWSFed::test_sp_initiated_implicit
Stack Traces | 110s run time
self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="14755faa101fdae073e155364aed1c75", element="f.8A3DAF58FC111CFA621C4C77A5D1C416.d.D1F3FDA656F018F37D08594CB2428613.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f58fbf3f250>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f58f8353a00>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x5605efb35302 <unknown>
E       #1 0x5605ef50a0c6 <unknown>
E       #2 0x5605ef55c25c <unknown>
E       #3 0x5605ef55c4a5 <unknown>
E       #4 0x5605ef55061a <unknown>
E       #5 0x5605ef581861 <unknown>
E       #6 0x5605ef5504e2 <unknown>
E       #7 0x5605ef581ba2 <unknown>
E       #8 0x5605ef5a3d7d <unknown>
E       #9 0x5605ef5815d7 <unknown>
E       #10 0x5605ef54e8b2 <unknown>
E       #11 0x5605ef54f725 <unknown>
E       #12 0x5605efaf8d44 <unknown>
E       #13 0x5605efafc086 <unknown>
E       #14 0x5605efafbb3e <unknown>
E       #15 0x5605efafc4f9 <unknown>
E       #16 0x5605efae86fa <unknown>
E       #17 0x5605efafc87a <unknown>
E       #18 0x5605efad0e49 <unknown>
E       #19 0x5605efb21b79 <unknown>
E       #20 0x5605efb21d6d <unknown>
E       #21 0x5605efb33903 <unknown>
E       #22 0x7ff6d6104469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f58f9c4e660>
test_case = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
result = <TestCaseFunction test_sp_initiated_implicit>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
method = <bound method TestProviderWSFed.test_sp_initiated_implicit of <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>,)
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_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>,)
kwargs = {}, file = 'default/flow-default-provider-invalidation.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Provider invalidation flow\nentries:\n- attrs:\n    designation: invalidatio...ation: none\n  identifiers:\n    slug: default-provider-invalidation-flow\n  model: authentik_flows.flow\n  id: 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: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>,)
kwargs = {}, file = 'system/providers-saml.yaml'
content = 'version: 1\nmetadata:\n  labels:\n    blueprints.goauthentik.io/system: "true"\n  name: System - SAML Provider - Mapp...rosoft..../identity/claims/windowsaccountname"\n      expression: |\n        return request.user.username\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_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>,)
kwargs = {}, config = <AuthentikCryptoConfig: authentik_crypto>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^

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

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-provider-authorization-implicit-consent.yaml",
        "default/flow-default-provider-invalidation.yaml",
    )
    @apply_blueprint(
        "system/providers-saml.yaml",
    )
    @reconcile_app("authentik_crypto")
    def test_sp_initiated_implicit(self):
        """test WSFed Provider flow SP-initiated flow (implicit consent)"""
        # Bootstrap all needed objects
        authorization_flow = Flow.objects.get(
            slug="default-provider-authorization-implicit-consent"
        )
        invalidation_flow = Flow.objects.get(slug="default-provider-invalidation-flow")
        provider = WSFederationProvider.objects.create(
            name=generate_id(),
            acs_url="http://localhost:8080",
            audience=self.realm,
            authorization_flow=authorization_flow,
            invalidation_flow=invalidation_flow,
            signing_kp=create_test_cert(),
        )
        provider.property_mappings.set(SAMLPropertyMapping.objects.all())
        provider.save()
        app = Application.objects.create(
            name="WSFed",
            slug=generate_id(),
            provider=provider,
        )
        self.setup_client(provider, app)
        self.driver.get("http://localhost:8080")
>       self.login()

tests/e2e/test_provider_ws_fed.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="14755faa101fdae073e155364aed1c75", element="f.8A3DAF58FC111CFA621C4C77A5D1C416.d.D1F3FDA656F018F37D08594CB2428613.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_ws_fed.TestProviderWSFed testMethod=test_sp_initiated_implicit>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit::test_oidcc_implicit_certification_test_plan
Stack Traces | 113s run time
self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="c89a42cdfcd6e5140791afe04bec6f92", element="f.8AAE703CA362576469464A87A877FD2E.d.F38012B7A3D5BB5D6465103C9AF239EE.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f37139d0f50>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f370f7edbc0>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55fea67d0302 <unknown>
E       #1 0x55fea61a50c6 <unknown>
E       #2 0x55fea61f725c <unknown>
E       #3 0x55fea61f74a5 <unknown>
E       #4 0x55fea61eb61a <unknown>
E       #5 0x55fea621c861 <unknown>
E       #6 0x55fea61eb4e2 <unknown>
E       #7 0x55fea621cba2 <unknown>
E       #8 0x55fea623ed7d <unknown>
E       #9 0x55fea621c5d7 <unknown>
E       #10 0x55fea61e98b2 <unknown>
E       #11 0x55fea61ea725 <unknown>
E       #12 0x55fea6793d44 <unknown>
E       #13 0x55fea6797086 <unknown>
E       #14 0x55fea6796b3e <unknown>
E       #15 0x55fea67974f9 <unknown>
E       #16 0x55fea67836fa <unknown>
E       #17 0x55fea679787a <unknown>
E       #18 0x55fea676be49 <unknown>
E       #19 0x55fea67bcb79 <unknown>
E       #20 0x55fea67bcd6d <unknown>
E       #21 0x55fea67ce903 <unknown>
E       #22 0x7f173218f469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f3719747620>
test_case = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
result = <TestCaseFunction test_oidcc_implicit_certification_test_plan>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
method = <bound method TestOpenIDConformanceImplicit.test_oidcc_implicit_certification_test_plan of <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>

    @retry()
    def test_oidcc_implicit_certification_test_plan(self):
>       self.run_test(
            "oidcc-implicit-certification-test-plan",
            self.test_plan_config,
            {
                "server_metadata": "discovery",
                "client_registration": "static_client",
            },
        )

tests/openid_conformance/test_oidc_implicit.py:9: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
test_name = 'oidcc-implicit-certification-test-plan'
test_plan_config = {'alias': 'authentik', 'client': {'client_id': '4054d882aff59755f2f279968b97ce8806a926e1', 'client_secret': '4c7e49330...86b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867'}, ...}
test_variant = {'client_registration': 'static_client', 'server_metadata': 'discovery'}

    def run_test(
        self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
    ):
        self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
    
        test_plan = self.conformance.create_test_plan(
            test_name,
            test_plan_config,
            test_variant,
        )
        plan_id = test_plan["id"]
        for test in test_plan["modules"]:
            # Fetch name and variant of the next test to run
            module_name = test["testModule"]
            variant = test["variant"]
            module_instance = self.conformance.create_test_from_plan_with_variant(
                plan_id, module_name, variant
            )
            module_id = module_instance["id"]
>           self.run_single_test(module_id)

tests/openid_conformance/base.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
module_id = 'J7QnXYd4xaw5yJB'

    def run_single_test(self, module_id: str):
        """Process instructions for a single test, navigate to browser URLs and take screenshots"""
        tested_browser_url = 0
        uploaded_image = 0
        cleared_cookies = False
        while True:
            # Fetch all info
            test_status = self.conformance.get_test_status(module_id)
            test_log = self.conformance.get_test_log(module_id)
            test_info = self.conformance.get_module_info(module_id)
            # Check status early, if we're finished already we don't want to do anything extra
            if test_info["status"] in ["INTERRUPTED", "FINISHED"]:
                return
            # Check if we need to clear cookies - tests only indicates this in their written summary
            # so this check is a bit brittle
            if "cookies" in test_info["summary"] and not cleared_cookies:
                # Navigate to our origin to delete cookies in the right context
                self.driver.get(self.url("authentik_api:user-me") + "?format=json")
                self.driver.delete_all_cookies()
                cleared_cookies = True
            # Check if we need deal with any browser URLs
            browser_urls = test_status.get("browser", {}).get("urls", [])
            if len(browser_urls) > tested_browser_url:
>               self.do_browser(browser_urls[tested_browser_url])

tests/openid_conformance/base.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
url = 'https://10.1.0.247:.../application/o/authorize/?client_id=4054d882aff59755f2f279968b97ce8806a926e1&nonce=L9YqH2ikT8&redirect_uri=https://localhost:.../a/authentik/callback&response_type=id_token&scope=openid&state=1SIfFkY7Xm'

    def do_browser(self, url):
        """For any specific OpenID Conformance test, execute the operations required"""
        self.driver.get(url)
        should_expect_completion = False
        if "if/flow/default-authentication-flow" in self.driver.current_url:
            self.logger.debug("Logging in")
            skipped = []
            if "login_hint" in self.driver.current_url:
                skipped.append("ak-stage-identification")
>           self.login(skip_stages=skipped)

tests/openid_conformance/base.py:143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="c89a42cdfcd6e5140791afe04bec6f92", element="f.8AAE703CA362576469464A87A877FD2E.d.F38012B7A3D5BB5D6465103C9AF239EE.e.11")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_implicit.TestOpenIDConformanceImplicit testMethod=test_oidcc_implicit_certification_test_plan>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated::test_oidcc_rp_initiated_certification_test_plan
Stack Traces | 114s run time
self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="185fd97734c23d55388304b140c2a47c", element="f.D58959D2449AC32AB0852AB3309C3A81.d.8126F8666C91BD47FF68E1531B9B3498.e.6")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7fa676c30050>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7fa66d5b1f30>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x557b4dcd5302 <unknown>
E       #1 0x557b4d6aa0c6 <unknown>
E       #2 0x557b4d6fc25c <unknown>
E       #3 0x557b4d6fc4a5 <unknown>
E       #4 0x557b4d6f061a <unknown>
E       #5 0x557b4d721861 <unknown>
E       #6 0x557b4d6f04e2 <unknown>
E       #7 0x557b4d721ba2 <unknown>
E       #8 0x557b4d743d7d <unknown>
E       #9 0x557b4d7215d7 <unknown>
E       #10 0x557b4d6ee8b2 <unknown>
E       #11 0x557b4d6ef725 <unknown>
E       #12 0x557b4dc98d44 <unknown>
E       #13 0x557b4dc9c086 <unknown>
E       #14 0x557b4dc9bb3e <unknown>
E       #15 0x557b4dc9c4f9 <unknown>
E       #16 0x557b4dc886fa <unknown>
E       #17 0x557b4dc9c87a <unknown>
E       #18 0x557b4dc70e49 <unknown>
E       #19 0x557b4dcc1b79 <unknown>
E       #20 0x557b4dcc1d6d <unknown>
E       #21 0x557b4dcd3903 <unknown>
E       #22 0x7f9d83385469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7fa67464fb60>
test_case = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
result = <TestCaseFunction test_oidcc_rp_initiated_certification_test_plan>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
method = <bound method TestOpenIDConformanceRPInitiated.test_oidcc_rp_initiated_certification_test_plan of <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>

    @retry()
    def test_oidcc_rp_initiated_certification_test_plan(self):
>       self.run_test(
            "oidcc-rp-initiated-logout-certification-test-plan",
            self.test_plan_config,
            {
                "client_registration": "static_client",
                "response_type": "code",
            },
        )

tests/openid_conformance/test_oidc_rp_initiated.py:17: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
test_name = 'oidcc-rp-initiated-logout-certification-test-plan'
test_plan_config = {'alias': 'authentik', 'client': {'client_id': '4054d882aff59755f2f279968b97ce8806a926e1', 'client_secret': '4c7e49330...86b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867'}, ...}
test_variant = {'client_registration': 'static_client', 'response_type': 'code'}

    def run_test(
        self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
    ):
        self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
    
        test_plan = self.conformance.create_test_plan(
            test_name,
            test_plan_config,
            test_variant,
        )
        plan_id = test_plan["id"]
        for test in test_plan["modules"]:
            # Fetch name and variant of the next test to run
            module_name = test["testModule"]
            variant = test["variant"]
            module_instance = self.conformance.create_test_from_plan_with_variant(
                plan_id, module_name, variant
            )
            module_id = module_instance["id"]
>           self.run_single_test(module_id)

tests/openid_conformance/base.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
module_id = 'qXrS9G3ptXuxYsJ'

    def run_single_test(self, module_id: str):
        """Process instructions for a single test, navigate to browser URLs and take screenshots"""
        tested_browser_url = 0
        uploaded_image = 0
        cleared_cookies = False
        while True:
            # Fetch all info
            test_status = self.conformance.get_test_status(module_id)
            test_log = self.conformance.get_test_log(module_id)
            test_info = self.conformance.get_module_info(module_id)
            # Check status early, if we're finished already we don't want to do anything extra
            if test_info["status"] in ["INTERRUPTED", "FINISHED"]:
                return
            # Check if we need to clear cookies - tests only indicates this in their written summary
            # so this check is a bit brittle
            if "cookies" in test_info["summary"] and not cleared_cookies:
                # Navigate to our origin to delete cookies in the right context
                self.driver.get(self.url("authentik_api:user-me") + "?format=json")
                self.driver.delete_all_cookies()
                cleared_cookies = True
            # Check if we need deal with any browser URLs
            browser_urls = test_status.get("browser", {}).get("urls", [])
            if len(browser_urls) > tested_browser_url:
>               self.do_browser(browser_urls[tested_browser_url])

tests/openid_conformance/base.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
url = 'https://10.1.0.227:.../application/o/authorize/?client_id=4054d882aff59755f2f279968b97ce8806a926e1&nonce=ObvXAqVrDG...Lgq5B3P05RJp5aZiyN63ceFLNTX1s7OoNTu06UohsA1Mali6PCmudv0APno37iNrloYEXVTMqqMvKNZwwZQzAWx02AkmeirJiUZQdNthKf3klJlW4v-._~'

    def do_browser(self, url):
        """For any specific OpenID Conformance test, execute the operations required"""
        self.driver.get(url)
        should_expect_completion = False
        if "if/flow/default-authentication-flow" in self.driver.current_url:
            self.logger.debug("Logging in")
            skipped = []
            if "login_hint" in self.driver.current_url:
                skipped.append("ak-stage-identification")
>           self.login(skip_stages=skipped)

tests/openid_conformance/base.py:143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="185fd97734c23d55388304b140c2a47c", element="f.D58959D2449AC32AB0852AB3309C3A81.d.8126F8666C91BD47FF68E1531B9B3498.e.6")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_rp_initiated.TestOpenIDConformanceRPInitiated testMethod=test_oidcc_rp_initiated_certification_test_plan>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel::test_oidcc_backchannel_logout_certification_test_plan
Stack Traces | 115s run time
self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="323b01fa2859a2f5e9617f2b0324c3e1", element="f.B6C825A462C2723738CB0BFC5E7E7524.d.87138E54E1F10FE9A47B0FC2E79C5F66.e.12")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7ff747ebccd0>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7ff74488e820>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x558067cba302 <unknown>
E       #1 0x55806768f0c6 <unknown>
E       #2 0x5580676e125c <unknown>
E       #3 0x5580676e14a5 <unknown>
E       #4 0x5580676d561a <unknown>
E       #5 0x558067706861 <unknown>
E       #6 0x5580676d54e2 <unknown>
E       #7 0x558067706ba2 <unknown>
E       #8 0x558067728d7d <unknown>
E       #9 0x5580677065d7 <unknown>
E       #10 0x5580676d38b2 <unknown>
E       #11 0x5580676d4725 <unknown>
E       #12 0x558067c7dd44 <unknown>
E       #13 0x558067c81086 <unknown>
E       #14 0x558067c80b3e <unknown>
E       #15 0x558067c814f9 <unknown>
E       #16 0x558067c6d6fa <unknown>
E       #17 0x558067c8187a <unknown>
E       #18 0x558067c55e49 <unknown>
E       #19 0x558067ca6b79 <unknown>
E       #20 0x558067ca6d6d <unknown>
E       #21 0x558067cb8903 <unknown>
E       #22 0x7ff936408469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7ff7462be510>
test_case = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
subTest = False

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

.../hostedtoolcache/Python/3.14.4.............../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
result = <TestCaseFunction test_oidcc_backchannel_logout_certification_test_plan>

    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.14.4.............../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
method = <bound method TestOpenIDConformanceBackchannel.test_oidcc_backchannel_logout_certification_test_plan of <tests.openid_...st_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4.............../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>,)
keywargs = {}
newargs = (<tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>,)
newkeywargs = {}

    @wraps(func)
    def patched(*args, **keywargs):
        with self.decoration_helper(patched,
                                    args,
                                    keywargs) as (newargs, newkeywargs):
>           return func(*newargs, **newkeywargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.../hostedtoolcache/Python/3.14.4.............../x64/lib/python3.14/unittest/mock.py:1439: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>

    @retry()
    def test_oidcc_backchannel_logout_certification_test_plan(self):
>       self.run_test(
            "oidcc-backchannel-rp-initiated-logout-certification-test-plan",
            self.test_plan_config,
            {
                "client_registration": "static_client",
                "response_type": "code",
            },
        )

tests/openid_conformance/test_oidc_backchannel.py:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
test_name = 'oidcc-backchannel-rp-initiated-logout-certification-test-plan'
test_plan_config = {'alias': 'authentik', 'client': {'client_id': '4054d882aff59755f2f279968b97ce8806a926e1', 'client_secret': '4c7e49330...86b5389d15b173109a0555dc47e0cc0949104f1925bcc6565351cb1dffd7e6818cf074f5bd50c210b565121a7328ee8bd40107fc4bbd867'}, ...}
test_variant = {'client_registration': 'static_client', 'response_type': 'code'}

    def run_test(
        self, test_name: str, test_plan_config: dict[str, Any], test_variant: dict[str, Any]
    ):
        self.conformance = Conformance(f"https://{self.host}:8443/", None, verify_ssl=False)
    
        test_plan = self.conformance.create_test_plan(
            test_name,
            test_plan_config,
            test_variant,
        )
        plan_id = test_plan["id"]
        for test in test_plan["modules"]:
            # Fetch name and variant of the next test to run
            module_name = test["testModule"]
            variant = test["variant"]
            module_instance = self.conformance.create_test_from_plan_with_variant(
                plan_id, module_name, variant
            )
            module_id = module_instance["id"]
>           self.run_single_test(module_id)

tests/openid_conformance/base.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
module_id = '6YF5poyPoDy1OjF'

    def run_single_test(self, module_id: str):
        """Process instructions for a single test, navigate to browser URLs and take screenshots"""
        tested_browser_url = 0
        uploaded_image = 0
        cleared_cookies = False
        while True:
            # Fetch all info
            test_status = self.conformance.get_test_status(module_id)
            test_log = self.conformance.get_test_log(module_id)
            test_info = self.conformance.get_module_info(module_id)
            # Check status early, if we're finished already we don't want to do anything extra
            if test_info["status"] in ["INTERRUPTED", "FINISHED"]:
                return
            # Check if we need to clear cookies - tests only indicates this in their written summary
            # so this check is a bit brittle
            if "cookies" in test_info["summary"] and not cleared_cookies:
                # Navigate to our origin to delete cookies in the right context
                self.driver.get(self.url("authentik_api:user-me") + "?format=json")
                self.driver.delete_all_cookies()
                cleared_cookies = True
            # Check if we need deal with any browser URLs
            browser_urls = test_status.get("browser", {}).get("urls", [])
            if len(browser_urls) > tested_browser_url:
>               self.do_browser(browser_urls[tested_browser_url])

tests/openid_conformance/base.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
url = 'https://10.1.1.160:.../application/o/authorize/?client_id=4054d882aff59755f2f279968b97ce8806a926e1&nonce=OoieDfiwjg...uLk2OJUFc55zLxGHo3XH7zVlEa7Ibx9PEZOOVOlvqbDpoatqhtVmnRNwRnb5jUOmnmgAiAYDSJrlpPsrf1hlFncnHKoYQAbLf9NQR5p3Zf9sXOQU3Z-._~'

    def do_browser(self, url):
        """For any specific OpenID Conformance test, execute the operations required"""
        self.driver.get(url)
        should_expect_completion = False
        if "if/flow/default-authentication-flow" in self.driver.current_url:
            self.logger.debug("Logging in")
            skipped = []
            if "login_hint" in self.driver.current_url:
                skipped.append("ak-stage-identification")
>           self.login(skip_stages=skipped)

tests/openid_conformance/base.py:143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="323b01fa2859a2f5e9617f2b0324c3e1", element="f.B6C825A462C2723738CB0BFC5E7E7524.d.87138E54E1F10FE9A47B0FC2E79C5F66.e.12")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.openid_conformance.test_oidc_backchannel.TestOpenIDConformanceBackchannel testMethod=test_oidcc_backchannel_logout_certification_test_plan>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4.............../x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_endpoints_flow.TestEndpointsFlow::test_login
Stack Traces | 116s run time
self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="904bc6919e0c81a669f7c924e8eebf22", element="f.EB2C1E07D7FE78C2A0C1A74C0B900067.d.864966131C17C899DE6BADA7690DAB5D.e.8")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f1f3fa0dbd0>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f1f3d3717a0>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x56387c2b7302 <unknown>
E       #1 0x56387bc8c0c6 <unknown>
E       #2 0x56387bcde25c <unknown>
E       #3 0x56387bcde4a5 <unknown>
E       #4 0x56387bcd261a <unknown>
E       #5 0x56387bd03861 <unknown>
E       #6 0x56387bcd24e2 <unknown>
E       #7 0x56387bd03ba2 <unknown>
E       #8 0x56387bd25d7d <unknown>
E       #9 0x56387bd035d7 <unknown>
E       #10 0x56387bcd08b2 <unknown>
E       #11 0x56387bcd1725 <unknown>
E       #12 0x56387c27ad44 <unknown>
E       #13 0x56387c27e086 <unknown>
E       #14 0x56387c27db3e <unknown>
E       #15 0x56387c27e4f9 <unknown>
E       #16 0x56387c26a6fa <unknown>
E       #17 0x56387c27e87a <unknown>
E       #18 0x56387c252e49 <unknown>
E       #19 0x56387c2a3b79 <unknown>
E       #20 0x56387c2a3d6d <unknown>
E       #21 0x56387c2b5903 <unknown>
E       #22 0x7fa3d99d4469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f1f43aaf8c0>
test_case = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
result = <TestCaseFunction test_login>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
method = <bound method TestEndpointsFlow.test_login of <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>,)
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: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    def test_login(self):
        """test default login flow"""
        rc, output = self.driver_container.exec_run(
            ["ak-sysd", "domains", "join", "ak", "-a", self.live_server_url],
            user="root",
            environment={"AK_SYS_INSECURE_ENV_TOKEN": self.enrollment_token.key},
        )
        self.assertEqual(rc, 0, str(output))
    
        dev = Device.objects.first()
        self.assertIsNotNone(dev)
    
        stage = EndpointStage.objects.create(
            name=generate_id(), connector=self.connector, mode=StageMode.REQUIRED
        )
        FlowStageBinding.objects.create(
            target=Flow.objects.get(slug="default-authentication-flow"), stage=stage, order=0
        )
    
        self.driver.get(
            self.url(
                "authentik_core:if-flow",
                flow_slug="default-authentication-flow",
            )
        )
>       self.login()

tests/e2e/test_endpoints_flow.py:59: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="904bc6919e0c81a669f7c924e8eebf22", element="f.EB2C1E07D7FE78C2A0C1A74C0B900067.d.864966131C17C899DE6BADA7690DAB5D.e.8")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_endpoints_flow.TestEndpointsFlow testMethod=test_login>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError
tests.e2e.test_provider_rac.TestProviderRAC::test_rac_ssh
Stack Traces | 122s run time
self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="80dce326a62fc6c5e1a7f2858197505c", element="f.83F30747E66CF4B6096552A5DE83E30D.d.067DF37FF5A76AA638707354D2F2E870.e.6")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
>           host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/selenium.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <[AttributeError("'ShadowRoot' object has no attribute 'session_id'") raised in repr()] WebDriverWait object at 0x7f961df08910>
method = <function SeleniumTestMixin.get_shadow_root.<locals>.<lambda> at 0x7f961f1249e0>
message = ''

    def until(self, method: Callable[[D], 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``.
    
        Args:
            method: A callable object that takes a WebDriver instance as an
                argument.
            message: Optional message for TimeoutException.
    
        Returns:
            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 0x55a8d7513302 <unknown>
E       #1 0x55a8d6ee80c6 <unknown>
E       #2 0x55a8d6f3a25c <unknown>
E       #3 0x55a8d6f3a4a5 <unknown>
E       #4 0x55a8d6f2e61a <unknown>
E       #5 0x55a8d6f5f861 <unknown>
E       #6 0x55a8d6f2e4e2 <unknown>
E       #7 0x55a8d6f5fba2 <unknown>
E       #8 0x55a8d6f81d7d <unknown>
E       #9 0x55a8d6f5f5d7 <unknown>
E       #10 0x55a8d6f2c8b2 <unknown>
E       #11 0x55a8d6f2d725 <unknown>
E       #12 0x55a8d74d6d44 <unknown>
E       #13 0x55a8d74da086 <unknown>
E       #14 0x55a8d74d9b3e <unknown>
E       #15 0x55a8d74da4f9 <unknown>
E       #16 0x55a8d74c66fa <unknown>
E       #17 0x55a8d74da87a <unknown>
E       #18 0x55a8d74aee49 <unknown>
E       #19 0x55a8d74ffb79 <unknown>
E       #20 0x55a8d74ffd6d <unknown>
E       #21 0x55a8d7511903 <unknown>
E       #22 0x7ff61fd95469 <unknown>

.venv/lib/python3.14.../webdriver/support/wait.py:121: TimeoutException

During handling of the above exception, another exception occurred:

self = <unittest.case._Outcome object at 0x7f961c2d1400>
test_case = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
subTest = False

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

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
result = <TestCaseFunction test_rac_ssh>

    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.14.4............/x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
method = <bound method TestProviderRAC.test_rac_ssh of <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
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/decorators.py:60: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>,)
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_provider_rac.TestProviderRAC testMethod=test_rac_ssh>,)
kwargs = {}, file = 'default/flow-default-provider-invalidation.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Provider invalidation flow\nentries:\n- attrs:\n    designation: invalidatio...ation: none\n  identifiers:\n    slug: default-provider-invalidation-flow\n  model: authentik_flows.flow\n  id: 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: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>,)
kwargs = {}, file = 'system/providers-rac.yaml'
content = 'version: 1\nmetadata:\n  labels:\n    blueprints.goauthentik.io/system: "true"\n  name: System - RAC Provider - Mappi...uthentik default RAC Mapping: SSH Default settings"\n      static_settings:\n        terminal-type: "xterm-256color"\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_provider_rac.TestProviderRAC testMethod=test_rac_ssh>,)
kwargs = {}, config = <AuthentikCryptoConfig: authentik_crypto>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^

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

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint(
        "default/flow-default-provider-authorization-implicit-consent.yaml",
        "default/flow-default-provider-invalidation.yaml",
    )
    @apply_blueprint(
        "system/providers-rac.yaml",
    )
    @reconcile_app("authentik_crypto")
    def test_rac_ssh(self):
        """Test SSH RAC"""
        test_ssh = self.run_container(
            image="lscr.io/linuxserver/openssh-server:latest",
            ports={
                "2222": "2222",
            },
            environment={
                "USER_NAME": "authentik",
                "USER_PASSWORD": self.password,
                "PASSWORD_ACCESS": "true",
                "SUDO_ACCESS": "true",
            },
        )
    
        rac: RACProvider = RACProvider.objects.create(
            name=generate_id(),
            authorization_flow=Flow.objects.get(
                slug="default-provider-authorization-implicit-consent"
            ),
            delete_token_on_disconnect=True,
        )
        endpoint = Endpoint.objects.create(
            name=generate_id(),
            protocol=Protocols.SSH,
            host=f"{self.host}:2222",
            settings={
                "username": "authentik",
                "password": self.password,
            },
            provider=rac,
        )
        app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=rac)
        outpost: Outpost = Outpost.objects.create(
            name=generate_id(),
            type=OutpostType.RAC,
        )
        outpost.providers.add(rac)
        outpost.build_user_permissions(outpost.user)
    
        self.start_rac(outpost)
    
        self.driver.get(
            self.url("authentik_providers_rac:start", app=app.slug, endpoint=endpoint.pk)
        )
>       self.login()

tests/e2e/test_provider_rac.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
shadow_dom = True, skip_stages = []

    def login(self, shadow_dom=True, skip_stages: list[str] | None = None):
        """Perform the entire authentik login flow."""
        skip_stages = skip_stages or []
    
        if "ak-stage-identification" not in skip_stages:
            if shadow_dom:
                flow_executor = self.get_shadow_root("ak-flow-executor")
>               identification_stage = self.get_shadow_root(
                    "ak-stage-identification", flow_executor
                )

tests/selenium.py:294: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
selector = 'ak-stage-identification'
container = <selenium.webdriver.remote.shadowroot.ShadowRoot (session="80dce326a62fc6c5e1a7f2858197505c", element="f.83F30747E66CF4B6096552A5DE83E30D.d.067DF37FF5A76AA638707354D2F2E870.e.6")>
timeout = 10

    def get_shadow_root(
        self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10
    ) -> WebElement:
        """Get the shadow root of a web component specified by `selector`."""
        if not container:
            container = self.driver
        wait = WebDriverWait(container, timeout)
        host: WebElement | None = None
    
        try:
            host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector))
        except TimeoutException:
>           self.fail(f"Timed out waiting for shadow host {selector} to appear")

tests/selenium.py:246: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_rac.TestProviderRAC testMethod=test_rac_ssh>
msg = 'Timed out waiting for shadow host ak-stage-identification to appear'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Timed out waiting for shadow host ak-stage-identification to appear

.../hostedtoolcache/Python/3.14.4............/x64/lib/python3.14/unittest/case.py:750: AssertionError

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

# WHAT

Fixes a number of nitpicks with the code:

1.  Removes the sentinel when our node is disconnected while the component remains connected; this happens when a predicated chooses to, and then not to, render a part of the host template.
2.  Avoids re-rendering the slot when it’s already present.
3.  Fixes a type-checking issue in `light` directive that forbade the DOM elements from being exported correctly.
4.  Removes dead code about `#form` from the Executor.
5.  Removes a stray semicolon from the footer template.
@kensternberg-authentik kensternberg-authentik marked this pull request as ready for review May 7, 2026 16:23
@kensternberg-authentik kensternberg-authentik requested review from a team as code owners May 7, 2026 16:23
* main: (25 commits)
  lifecycle/aws: bump aws-cdk from 2.1119.0 to 2.1120.0 in /lifecycle/aws (#22105)
  web: bump @sentry/browser from 10.50.0 to 10.51.0 in /web in the sentry group across 1 directory (#22106)
  ci: bump taiki-e/install-action from 2.75.30 to 2.76.0 in /.github/actions/setup (#22108)
  core, web: update translations (#22129)
  stage/authenticator*: expand attempt throttling to email- and sms-based 2FA (#21751)
  translate: Updates for project authentik and language pt_PT (#22122)
  translate: Updates for project authentik and language no_NO (#22120)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#22128)
  translate: Updates for project authentik and language ja_JP (#22118)
  translate: Updates for project authentik and language ru_RU (#22119)
  translate: Updates for project authentik and language tr_TR (#22125)
  translate: Updates for project authentik and language pl_PL (#22124)
  translate: Updates for project authentik and language pt_BR (#22111)
  translate: Updates for project authentik and language it_IT (#22123)
  translate: Updates for project authentik and language zh-Hans (#22121)
  translate: Updates for project authentik and language cs_CZ (#22115)
  translate: Updates for project authentik and language fr_FR (#22117)
  translate: Updates for project authentik and language fi_FI (#22114)
  translate: Updates for project authentik and language de_DE (#22113)
  translate: Updates for project authentik and language es_ES (#22116)
  ...
* main:
  locale: fix de_DE locale placeholder (#22130)
  website/docs: stages cleanup (#21558)
  website: bump the build group in /website with 3 updates (#22104)
  web: bump basic-ftp from 5.3.0 to 5.3.1 in /web (#22131)
  web: bump knip from 6.7.0 to 6.9.0 in /web (#22107)
# What

1.  Rename `ak-brand-footer` to `ak-brand-links`; this brings the filename in-line with the component name.

2.  Clean up the imports; the initial load does not need both ak-flow and ak-flow-executor, since the first imports the second. ak-flow does not need ak-flow-card; in fact, ak-flow-executor should not need it either, since that’s a stage thing. On the other hand, `ak-brand-links` *does* need to be in the `index.entrypoint.ts` file, since it will be needed by the Django template.
@kensternberg-authentik kensternberg-authentik requested review from a team as code owners May 7, 2026 22:16
@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for authentik-integrations ready!

Name Link
🔨 Latest commit 3d38c5b
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/69fd0f5c2719970008d144d5
😎 Deploy Preview https://deploy-preview-22103--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.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit 3d38c5b
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69fd0f5c0c43f500075b9011
😎 Deploy Preview https://deploy-preview-22103--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.

@kensternberg-authentik kensternberg-authentik changed the base branch from web/element/new-drawer to main May 7, 2026 22:19
@kensternberg-authentik kensternberg-authentik changed the base branch from main to web/element/new-drawer May 7, 2026 22:19
…customizing CSS.

Updated the documenting comment to show the changes and call out the use of `light()` as an
idiom.

\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant