Skip to content

test: adapt UI e2e tests to prod environment#1271

Open
rsoaresd wants to merge 5 commits intocodeready-toolchain:masterfrom
rsoaresd:adapt_ui_e2e_tests_to_prod_needs
Open

test: adapt UI e2e tests to prod environment#1271
rsoaresd wants to merge 5 commits intocodeready-toolchain:masterfrom
rsoaresd:adapt_ui_e2e_tests_to_prod_needs

Conversation

@rsoaresd
Copy link
Contributor

@rsoaresd rsoaresd commented Mar 25, 2026

Description

Adapt UI E2E tests to run properly against production environment

Issue ticket number and link

SANDBOX-1682

Summary by CodeRabbit

  • New Features

    • Added ability to run end-to-end tests against the production environment and a containerized prod test flow.
  • Tests

    • Enhanced production signup cleanup and verification, with explicit timeouts and alternate OAuth/assertion paths.
    • Added username masking during test flows and improved test artifact handling.
  • Documentation

    • Added instructions for running E2E tests against production, including required credentials.
  • Chores

    • Made environment/config injection more flexible (KUBECONFIG/ENVIRONMENT).

@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

Walkthrough

Adds production-capable E2E test support for the Developer Sandbox Dashboard: externalizes environment variables, adds prod Make targets, introduces ksctl-based user-signup management, applies username masking, and adapts test/login/popup flows and verifications for prod OAuth/guided-tour behavior.

Changes

Cohort / File(s) Summary
Configuration & Make
deploy/devsandbox-dashboard/ui-e2e-tests/.env, make/devsandbox-dashboard.mk
Externalized ENVIRONMENT and added KUBECONFIG support. Added test-devsandbox-dashboard-e2e-prod and test-devsandbox-dashboard-in-container-prod Make targets (prod BASE_URL/ENVIRONMENT, containerized run with mounted kubeconfig and SSO env).
Documentation
test/e2e/devsandbox-dashboard/README.md
Added “Deploy Developer Sandbox Dashboard in Prod” instructions showing how to run prod E2E via the new Make target and required credentials/tools (SSO_*, KUBECONFIG, ksctl).
Test behavior: signup & verification
test/e2e/devsandbox-dashboard/fresh_signup_test.go
Added prod-specific signup cleanup using ksctl-get/delete, switched to t.Cleanup for prod deletions, increased loading wait timeout to 60s, and branched verification to handle OAuth redirect and guided-tour modal/assertions for prod.
Test helpers: ksctl ops
testsupport/devsandbox-dashboard/usersignup.go
Added GetUserSignupThroughKsctl and DeleteUserSignupThroughKsctl (ksctl invocations, YAML parsing, GDPR delete with confirmation and polling). Simplified DeleteUserSignup logging.
Test helpers: login / popup / setup
testsupport/devsandbox-dashboard/login.go, testsupport/devsandbox-dashboard/popup.go, testsupport/devsandbox-dashboard/setup.go
Treat ProdEnv like DevEnv for login locators; move username masking to navigation and add 60s page goto timeout; call maskUsername before popup polling; add exported ProdEnv constant and gate trace collection by ARTIFACT_DIR; extend cookie-consent automation to prod.
Test helpers: privacy & traces
testsupport/devsandbox-dashboard/trace.go
Refactored video renaming (extracted popup path helper) and added maskUsername helper that injects a Playwright init script to blur/disable selection on username-related elements and observe DOM mutations.

Sequence Diagram(s)

sequenceDiagram
    participant TestRunner as Test Runner
    participant Browser as Browser (Playwright)
    participant SSO as SSO/OAuth
    participant ksctl as ksctl (CLI)    
    participant Kube as Kubernetes/API

    TestRunner->>Browser: start test, Navigate (maskUsername) -> goto(BASE_URL, timeout=60s)
    Browser->>SSO: redirect to /oauth/authorize (popup)
    TestRunner->>Browser: ClickAndWaitForPopup (maskUsername), expect popup
    Browser->>SSO: perform login (username/password)
    SSO-->>Browser: redirect back with project page
    Browser->>TestRunner: page shows guided-tour modal
    TestRunner->>ksctl: GetUserSignupThroughKsctl(username) (when cleaning)
    ksctl->>Kube: query UserSignup resource
    Kube-->>ksctl: YAML userSignup or "not found"
    TestRunner->>ksctl: DeleteUserSignupThroughKsctl(username) (gdpr-delete, confirm)
    ksctl->>Kube: delete UserSignup
    Kube-->>ksctl: deletion confirmation (poll until not found)
    ksctl-->>TestRunner: command output (deleted)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adapting UI E2E tests to work with the production environment.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
testsupport/devsandbox-dashboard/trace.go (1)

97-125: Make maskUsername() idempotent per browser context.

This helper installs a new init script every time it is called. LoginPage.Navigate() and ClickAndWaitForPopup() now both invoke it on the same context, so each popup open registers another copy of the script and another MutationObserver for future pages. Add a guard here so repeated calls become a no-op.

Possible minimal guard inside the injected script
 func maskUsername(t *testing.T, page playwright.Page) {
 	err := page.Context().AddInitScript(playwright.Script{
 		Content: playwright.String(`
+			if (window.__usernameMaskInitialized) {
+				return;
+			}
+			window.__usernameMaskInitialized = true;
+
 			const applyBlur = () => {
 				document.querySelectorAll('input[name="username"], span.co-username, span[data-test="username"], [data-test="username"]').forEach(el => {
 					el.style.filter = 'blur(5px)';
 					el.style.userSelect = 'none';
 				});
 			};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsupport/devsandbox-dashboard/trace.go` around lines 97 - 125,
maskUsername registers the same init script each call causing multiple
MutationObservers; modify the injected script (the one passed to
page.Context().AddInitScript in maskUsername) to be idempotent by
checking/setting a per-context flag (e.g. window.__maskUsernameInstalled) at the
top: if the flag is already true, return immediately; otherwise set it true and
then run applyBlur + create the MutationObserver. Keep the rest of the
applyBlur/observer logic intact so repeated calls become no-ops for that browser
context.
testsupport/devsandbox-dashboard/usersignup.go (1)

66-99: Consider extracting repeated command arguments to constants.

SonarCloud flagged the "--config" literal being duplicated 3 times (lines 48, 70, 81). You could extract common ksctl command arguments to reduce duplication.

♻️ Proposed refactor
+const (
+	ksctlConfigFlag = "--config"
+	ksctlHostTarget = "-t"
+)
+
 func GetUserSignupThroughKsctl(t *testing.T, username string) *toolchainv1alpha1.UserSignup {
 	t.Logf("Getting UserSignup through ksctl")
 
 	// `#nosec` G204 -- username is from test config, not user input
-	cmd := exec.Command("ksctl", "get", "usersignup", username, "--config", viper.GetString("KUBECONFIG"), "-t", "host", "-o", "yaml")
+	cmd := exec.Command("ksctl", "get", "usersignup", username, ksctlConfigFlag, viper.GetString("KUBECONFIG"), ksctlHostTarget, "host", "-o", "yaml")

Apply similarly to lines 70 and 81.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsupport/devsandbox-dashboard/usersignup.go` around lines 66 - 99, The
function DeleteUserSignupThroughKsctl repeats the same ksctl arguments (the
"--config" flag and viper.GetString("KUBECONFIG")) in multiple exec.Command
calls; extract the common args into a single reusable variable or helper (e.g.,
ksctlConfigArg := []string{"--config", viper.GetString("KUBECONFIG")} or a
buildKsctlCmd helper) and use it when constructing exec.Command for the initial
"ksctl gdpr-delete" call and the subsequent "ksctl get usersignup" calls to
remove duplication and make intent clearer while preserving existing command
composition and stdin behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@testsupport/devsandbox-dashboard/setup.go`:
- Around line 67-71: The current gate using ARTIFACT_DIR prevents trace(t,
context, testName) from running in CI and removes Playwright traces; instead
remove the ARTIFACT_DIR check and always call trace(...) (so traces continue to
be routed by getTraceDirectory()), or if you need to disable tracing only for
production, gate on env == ProdEnv or a dedicated opt-out flag (e.g.
DISABLE_TRACING) rather than ARTIFACT_DIR; update
testsupport/devsandbox-dashboard/setup.go to call trace(t, context, testName)
unconditionally or replace the ARTIFACT_DIR condition with the appropriate
ProdEnv/opt-out check.

---

Nitpick comments:
In `@testsupport/devsandbox-dashboard/trace.go`:
- Around line 97-125: maskUsername registers the same init script each call
causing multiple MutationObservers; modify the injected script (the one passed
to page.Context().AddInitScript in maskUsername) to be idempotent by
checking/setting a per-context flag (e.g. window.__maskUsernameInstalled) at the
top: if the flag is already true, return immediately; otherwise set it true and
then run applyBlur + create the MutationObserver. Keep the rest of the
applyBlur/observer logic intact so repeated calls become no-ops for that browser
context.

In `@testsupport/devsandbox-dashboard/usersignup.go`:
- Around line 66-99: The function DeleteUserSignupThroughKsctl repeats the same
ksctl arguments (the "--config" flag and viper.GetString("KUBECONFIG")) in
multiple exec.Command calls; extract the common args into a single reusable
variable or helper (e.g., ksctlConfigArg := []string{"--config",
viper.GetString("KUBECONFIG")} or a buildKsctlCmd helper) and use it when
constructing exec.Command for the initial "ksctl gdpr-delete" call and the
subsequent "ksctl get usersignup" calls to remove duplication and make intent
clearer while preserving existing command composition and stdin behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40b3f0e3-cf7f-4d4d-b833-c27d59928c58

📥 Commits

Reviewing files that changed from the base of the PR and between 629dc7b and 90600a0.

📒 Files selected for processing (9)
  • deploy/devsandbox-dashboard/ui-e2e-tests/.env
  • make/devsandbox-dashboard.mk
  • test/e2e/devsandbox-dashboard/README.md
  • test/e2e/devsandbox-dashboard/fresh_signup_test.go
  • testsupport/devsandbox-dashboard/login.go
  • testsupport/devsandbox-dashboard/popup.go
  • testsupport/devsandbox-dashboard/setup.go
  • testsupport/devsandbox-dashboard/trace.go
  • testsupport/devsandbox-dashboard/usersignup.go

Comment on lines +67 to +71
// save trace only if not running in CI
// we do not want to expose sensitive information in CI
if artifactDir := os.Getenv("ARTIFACT_DIR"); artifactDir == "" { // not CI environment
trace(t, context, testName)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't disable tracing for every CI run.

ARTIFACT_DIR is already how getTraceDirectory() routes trace artifacts into CI storage. Gating trace() on that variable removes Playwright traces from all CI jobs, including the existing ui-e2e-tests path, so failures lose the most useful debugging artifact. If the concern is prod secrecy, gate on env == ProdEnv or a dedicated opt-out flag instead of CI detection.

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 69-69: Remove this unnecessary variable declaration and use the expression directly in the condition.

See more on https://sonarcloud.io/project/issues?id=codeready-toolchain_toolchain-e2e&issues=AZ0mCgEWDAKfaaDBdd63&open=AZ0mCgEWDAKfaaDBdd63&pullRequest=1271

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsupport/devsandbox-dashboard/setup.go` around lines 67 - 71, The current
gate using ARTIFACT_DIR prevents trace(t, context, testName) from running in CI
and removes Playwright traces; instead remove the ARTIFACT_DIR check and always
call trace(...) (so traces continue to be routed by getTraceDirectory()), or if
you need to disable tracing only for production, gate on env == ProdEnv or a
dedicated opt-out flag (e.g. DISABLE_TRACING) rather than ARTIFACT_DIR; update
testsupport/devsandbox-dashboard/setup.go to call trace(t, context, testName)
unconditionally or replace the ARTIFACT_DIR condition with the appropriate
ProdEnv/opt-out check.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
testsupport/devsandbox-dashboard/setup.go (1)

67-71: ⚠️ Potential issue | 🟠 Major

Don't key tracing off ARTIFACT_DIR.

getTraceDirectory() already routes CI traces into ARTIFACT_DIR. This guard disables Playwright tracing in every CI job, so failed ui-e2e-tests runs lose the main debugging artifact. If only prod runs need different handling, gate on env == ProdEnv or a dedicated opt-out flag instead of CI detection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsupport/devsandbox-dashboard/setup.go` around lines 67 - 71, The current
guard that skips trace(t, context, testName) based on os.Getenv("ARTIFACT_DIR")
prevents CI jobs from saving Playwright traces; instead remove that ARTIFACT_DIR
check and either always call trace(...) or gate it on the production-only
condition (compare the current environment to ProdEnv) or a dedicated opt-out
flag (e.g. DISABLE_TRACING) so CI traces still get written via
getTraceDirectory(); update the logic around trace(...) and any callers
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@testsupport/devsandbox-dashboard/usersignup.go`:
- Around line 54-60: GetUserSignupThroughKsctl() and the delete-poll block
currently treat ksctl "not found" responses differently and swallow other
errors; update both places so they consistently detect NotFound
(case-insensitive match for "not found" and the exact "Error from server
(NotFound)") and treat that as the missing-resource success case, but for any
other non-NotFound error return the error immediately (fail fast) instead of
returning nil/false and continuing to poll; apply the same change to the other
occurrence referenced in the comment (the delete poll block around lines 87-100)
so both code paths behave the same.

---

Duplicate comments:
In `@testsupport/devsandbox-dashboard/setup.go`:
- Around line 67-71: The current guard that skips trace(t, context, testName)
based on os.Getenv("ARTIFACT_DIR") prevents CI jobs from saving Playwright
traces; instead remove that ARTIFACT_DIR check and either always call trace(...)
or gate it on the production-only condition (compare the current environment to
ProdEnv) or a dedicated opt-out flag (e.g. DISABLE_TRACING) so CI traces still
get written via getTraceDirectory(); update the logic around trace(...) and any
callers accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4b2b2ec-2c4c-4f02-9228-10653211e4ac

📥 Commits

Reviewing files that changed from the base of the PR and between 90600a0 and b7acc0f.

📒 Files selected for processing (3)
  • test/e2e/devsandbox-dashboard/fresh_signup_test.go
  • testsupport/devsandbox-dashboard/setup.go
  • testsupport/devsandbox-dashboard/usersignup.go

Comment on lines +54 to +60
if err != nil {
// Check if it's a not found error
if strings.Contains(string(output), "Error from server (NotFound)") {
return nil
}
}
require.NoError(t, err)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the ksctl NotFound handling consistent and fail fast on other errors.

GetUserSignupThroughKsctl() treats Error from server (NotFound) as missing, but the delete poll only matches lowercase not found and turns every other failure into return false, nil. A successful delete can therefore spin until the two-minute timeout, and real ksctl/KUBECONFIG failures get hidden until the timeout expires.

🛠️ Possible fix
+func ksctlUserSignupNotFound(output []byte, err error) bool {
+	text := strings.ToLower(string(output))
+	errText := ""
+	if err != nil {
+		errText = strings.ToLower(err.Error())
+	}
+
+	return strings.Contains(text, "not found") ||
+		strings.Contains(text, "notfound") ||
+		strings.Contains(errText, "not found") ||
+		strings.Contains(errText, "notfound")
+}
+
 func GetUserSignupThroughKsctl(t *testing.T, username string) *toolchainv1alpha1.UserSignup {
 	t.Logf("Getting UserSignup through ksctl")
 
 	// `#nosec` G204 -- username is from test config, not user input
 	cmd := exec.Command("ksctl", "get", "usersignup", username, configFlag, viper.GetString("KUBECONFIG"), "-t", "host", "-o", "yaml")
 	output, err := cmd.CombinedOutput()
-	if err != nil {
-		// Check if it's a not found error
-		if strings.Contains(string(output), "Error from server (NotFound)") {
-			return nil
-		}
+	if err != nil && ksctlUserSignupNotFound(output, err) {
+		return nil
 	}
 	require.NoError(t, err)
 
 	// parse the output as a UserSignup
 	userSignup := &toolchainv1alpha1.UserSignup{}
@@
 	err = k8swait.PollUntilContextTimeout(context.TODO(), time.Second, 2*time.Minute, true,
 		func(ctx context.Context) (done bool, err error) {
 			// `#nosec` G204 -- username is from test config, not user input
 			cmd := exec.Command("ksctl", "get", "usersignup", username, configFlag, viper.GetString("KUBECONFIG"), "-t", "host", "-o", "yaml")
 			output, err := cmd.CombinedOutput()
 			if err != nil {
-				// Check if it's a not found error
-				if strings.Contains(string(output), "not found") || strings.Contains(err.Error(), "not found") {
+				if ksctlUserSignupNotFound(output, err) {
 					t.Log("UserSignup successfully deleted")
 					return true, nil
 				}
-				// Some other error, keep trying
-				t.Log("Error checking UserSignup deletion")
-				return false, nil
+				return false, err
 			}
 			// Still exists
 			return false, nil
 		})

Also applies to: 87-100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testsupport/devsandbox-dashboard/usersignup.go` around lines 54 - 60,
GetUserSignupThroughKsctl() and the delete-poll block currently treat ksctl "not
found" responses differently and swallow other errors; update both places so
they consistently detect NotFound (case-insensitive match for "not found" and
the exact "Error from server (NotFound)") and treat that as the missing-resource
success case, but for any other non-NotFound error return the error immediately
(fail fast) instead of returning nil/false and continuing to poll; apply the
same change to the other occurrence referenced in the comment (the delete poll
block around lines 87-100) so both code paths behave the same.

@openshift-ci
Copy link

openshift-ci bot commented Mar 25, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: alexeykazakov, rsoaresd

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:
  • OWNERS [alexeykazakov,rsoaresd]

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots

See analysis details on SonarQube Cloud

@openshift-ci
Copy link

openshift-ci bot commented Mar 26, 2026

@rsoaresd: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e 4720dd0 link true /test e2e

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants