This is an automated email from the ASF dual-hosted git repository.

chanholee pushed a commit to branch branch-0.12
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/branch-0.12 by this push:
     new 2affc612bd [ZEPPELIN-6357] Fixed missing login prerequisite in each 
test
2affc612bd is described below

commit 2affc612bd79364a45a631fb151aed5346fc9c1c
Author: YONGJAE LEE(이용재) <[email protected]>
AuthorDate: Mon Oct 6 10:44:58 2025 +0900

    [ZEPPELIN-6357] Fixed missing login prerequisite in each test
    
    ### What is this PR for?
    
https://github.com/apache/zeppelin/actions/runs/18239525409/job/51939323284?pr=5095
    
    I recently found that the `run-playwright-e2e-tests (auth)` step wasn’t 
running properly during the New UI's E2E tests.
    The root cause was that the tests required a setup (tear-up) process.
    Since handling login in `globalSetup` within `playwright.config` made it 
difficult to account for all browser contexts,
    I added a 
method([performLoginIfRequired](https://github.com/apache/zeppelin/pull/5096/files#diff-10918824923e3beebfd460fdab4db4ee063a0f1ee10c49bb5829e459e3af31f3R164-R196))
 in `utils` to perform the login when a `shiro.ini` file exists, and made each 
test execute it in `beforeEach`.
    
    During this process, I also merged the `helper` into `utils`, as they 
essentially served the same purpose.
    
    ### What type of PR is it?
    Bug Fix
    Improvement
    
    ### Todos
    
    ### What is the Jira issue?
    ZEPPELIN-6357
    
    ### How should this be tested?
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    Closes #5096 from dididy/fix/login-tearup.
    
    Signed-off-by: ChanHo Lee <[email protected]>
    (cherry picked from commit 89aecde0ed381d5ff6c68ed299c1562e675445e5)
    Signed-off-by: ChanHo Lee <[email protected]>
---
 zeppelin-web-angular/e2e/helper.ts                 | 37 --------------
 zeppelin-web-angular/e2e/models/login-page.util.ts |  4 +-
 zeppelin-web-angular/e2e/tests/app.spec.ts         | 19 +++-----
 .../anonymous-login-redirect.spec.ts               | 55 ++++++++++++++-------
 .../e2e/tests/theme/dark-mode.spec.ts              | 27 +++++-----
 zeppelin-web-angular/e2e/utils.ts                  | 57 +++++++++++++++++++++-
 6 files changed, 118 insertions(+), 81 deletions(-)

diff --git a/zeppelin-web-angular/e2e/helper.ts 
b/zeppelin-web-angular/e2e/helper.ts
deleted file mode 100644
index ef971e7a92..0000000000
--- a/zeppelin-web-angular/e2e/helper.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *     http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { Page } from '@playwright/test';
-
-export class ZeppelinHelper {
-  constructor(private page: Page) {}
-
-  async waitForZeppelinReady(): Promise<void> {
-    try {
-      await this.page.waitForLoadState('networkidle', { timeout: 30000 });
-      await this.page.waitForFunction(
-        () => {
-          const hasAngular = document.querySelector('[ng-version]') !== null;
-          const hasZeppelinContent =
-            document.body.textContent?.includes('Zeppelin') ||
-            document.body.textContent?.includes('Notebook') ||
-            document.body.textContent?.includes('Welcome');
-          const hasZeppelinRoot = document.querySelector('zeppelin-root') !== 
null;
-          return hasAngular && (hasZeppelinContent || hasZeppelinRoot);
-        },
-        { timeout: 60 * 1000 }
-      );
-    } catch (error) {
-      throw error instanceof Error ? error : new Error(`Zeppelin loading 
failed: ${String(error)}`);
-    }
-  }
-}
diff --git a/zeppelin-web-angular/e2e/models/login-page.util.ts 
b/zeppelin-web-angular/e2e/models/login-page.util.ts
index 48ce3053d3..e5cce7fee5 100644
--- a/zeppelin-web-angular/e2e/models/login-page.util.ts
+++ b/zeppelin-web-angular/e2e/models/login-page.util.ts
@@ -98,7 +98,9 @@ export class LoginTestUtil {
 
   private static _parseUserLine(line: string, users: Record<string, 
TestCredentials>): void {
     const [userPart, ...roleParts] = line.split('=');
-    if (!userPart || roleParts.length === 0) return;
+    if (!userPart || roleParts.length === 0) {
+      return;
+    }
 
     const username = userPart.trim();
     const rightSide = roleParts.join('=').trim();
diff --git a/zeppelin-web-angular/e2e/tests/app.spec.ts 
b/zeppelin-web-angular/e2e/tests/app.spec.ts
index 8baa40123a..9231eca79e 100644
--- a/zeppelin-web-angular/e2e/tests/app.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/app.spec.ts
@@ -11,18 +11,15 @@
  */
 
 import { expect, test } from '@playwright/test';
-import { ZeppelinHelper } from '../helper';
 import { BasePage } from '../models/base-page';
 import { LoginTestUtil } from '../models/login-page.util';
-import { addPageAnnotationBeforeEach, PAGES } from '../utils';
+import { addPageAnnotationBeforeEach, waitForZeppelinReady, PAGES } from 
'../utils';
 
 test.describe('Zeppelin App Component', () => {
   addPageAnnotationBeforeEach(PAGES.APP);
-  let zeppelinHelper: ZeppelinHelper;
   let basePage: BasePage;
 
   test.beforeEach(async ({ page }) => {
-    zeppelinHelper = new ZeppelinHelper(page);
     basePage = new BasePage(page);
 
     await page.goto('/', { waitUntil: 'load' });
@@ -56,7 +53,7 @@ test.describe('Zeppelin App Component', () => {
   });
 
   test('should display workspace after loading', async ({ page }) => {
-    await zeppelinHelper.waitForZeppelinReady();
+    await waitForZeppelinReady(page);
     const isShiroEnabled = await LoginTestUtil.isShiroEnabled();
     if (isShiroEnabled) {
       await expect(page.locator('zeppelin-login')).toBeVisible();
@@ -66,7 +63,7 @@ test.describe('Zeppelin App Component', () => {
   });
 
   test('should handle navigation events correctly', async ({ page }) => {
-    await zeppelinHelper.waitForZeppelinReady();
+    await waitForZeppelinReady(page);
 
     // Test navigation back to root path
     try {
@@ -79,7 +76,7 @@ test.describe('Zeppelin App Component', () => {
       const spinnerCount = await loadingSpinner.count();
       expect(spinnerCount).toBeGreaterThanOrEqual(0);
 
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
 
       // After ready, loading should be hidden if it was visible
       if (await loadingSpinner.isVisible()) {
@@ -108,12 +105,12 @@ test.describe('Zeppelin App Component', () => {
     }
 
     // Wait for loading to complete
-    await zeppelinHelper.waitForZeppelinReady();
+    await waitForZeppelinReady(page);
     await expect(loadingSpinner).toBeHidden();
   });
 
   test('should handle logout observable correctly', async ({ page }) => {
-    await zeppelinHelper.waitForZeppelinReady();
+    await waitForZeppelinReady(page);
 
     const logoutSpinner = page.locator('zeppelin-spin').filter({ hasText: 
'Logging out' });
 
@@ -142,7 +139,7 @@ test.describe('Zeppelin App Component', () => {
   });
 
   test('should maintain component integrity during navigation', async ({ page 
}) => {
-    await zeppelinHelper.waitForZeppelinReady();
+    await waitForZeppelinReady(page);
 
     const zeppelinRoot = page.locator('zeppelin-root');
 
@@ -160,7 +157,7 @@ test.describe('Zeppelin App Component', () => {
         const routerOutlet = zeppelinRoot.locator('router-outlet');
         await expect(routerOutlet).toBeAttached();
 
-        await zeppelinHelper.waitForZeppelinReady();
+        await waitForZeppelinReady(page);
       } catch (error) {
         // Skip paths that don't exist or are not accessible
         console.log(`Skipping path ${path}: ${error}`);
diff --git 
a/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
 
b/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
index 34b9f498f0..ce66ce2f74 100644
--- 
a/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
+++ 
b/zeppelin-web-angular/e2e/tests/authentication/anonymous-login-redirect.spec.ts
@@ -11,25 +11,36 @@
  */
 
 import { expect, test } from '@playwright/test';
-import { ZeppelinHelper } from '../../helper';
 import { HomePageUtil } from '../../models/home-page.util';
-import { addPageAnnotationBeforeEach, PAGES, waitForUrlNotContaining, 
getCurrentPath } from '../../utils';
+import { LoginTestUtil } from '../../models/login-page.util';
+import {
+  addPageAnnotationBeforeEach,
+  getCurrentPath,
+  waitForUrlNotContaining,
+  waitForZeppelinReady,
+  PAGES
+} from '../../utils';
 
 test.describe('Anonymous User Login Redirect', () => {
   addPageAnnotationBeforeEach(PAGES.WORKSPACE.HOME);
 
-  let zeppelinHelper: ZeppelinHelper;
   let homePageUtil: HomePageUtil;
 
+  test.beforeAll(async () => {
+    const isShiroEnabled = await LoginTestUtil.isShiroEnabled();
+    if (isShiroEnabled) {
+      test.skip(true, 'Skipping anonymous login redirect tests - 
authentication is enabled (shiro.ini found)');
+    }
+  });
+
   test.beforeEach(async ({ page }) => {
-    zeppelinHelper = new ZeppelinHelper(page);
     homePageUtil = new HomePageUtil(page);
   });
 
   test.describe('Given an anonymous user is already logged in', () => {
     test.beforeEach(async ({ page }) => {
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
     });
 
     test('When accessing login page directly, Then should redirect to home 
with proper URL change', async ({
@@ -48,7 +59,7 @@ test.describe('Anonymous User Login Redirect', () => {
       page
     }) => {
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       await homePageUtil.verifyHomePageIntegrity();
@@ -56,7 +67,7 @@ test.describe('Anonymous User Login Redirect', () => {
 
     test('When clicking Zeppelin logo after redirect, Then should maintain 
home URL and content', async ({ page }) => {
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       const navigationResult = await homePageUtil.testNavigationConsistency();
@@ -69,7 +80,7 @@ test.describe('Anonymous User Login Redirect', () => {
 
     test('When accessing login page, Then should redirect and maintain 
anonymous user state', async ({ page }) => {
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       const metadata = await homePageUtil.getPageMetadata();
@@ -82,7 +93,7 @@ test.describe('Anonymous User Login Redirect', () => {
 
     test('When accessing login page, Then should display welcome heading and 
main sections', async ({ page }) => {
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       await expect(page.locator('h1', { hasText: 'Welcome to Zeppelin!' 
})).toBeVisible();
@@ -93,7 +104,7 @@ test.describe('Anonymous User Login Redirect', () => {
 
     test('When accessing login page, Then should display notebook 
functionalities', async ({ page }) => {
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       await expect(page.locator('text=Create new Note')).toBeVisible();
@@ -109,7 +120,7 @@ test.describe('Anonymous User Login Redirect', () => {
       page
     }) => {
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       const docLinks = page.locator('a[href*="zeppelin.apache.org/docs"]');
@@ -117,24 +128,32 @@ test.describe('Anonymous User Login Redirect', () => {
       const issuesLinks = page.locator('a[href*="issues.apache.org"]');
       const githubLinks = 
page.locator('a[href*="github.com/apache/zeppelin"]');
 
-      if ((await docLinks.count()) > 0) await expect(docLinks).toBeVisible();
-      if ((await communityLinks.count()) > 0) await 
expect(communityLinks).toBeVisible();
-      if ((await issuesLinks.count()) > 0) await 
expect(issuesLinks).toBeVisible();
-      if ((await githubLinks.count()) > 0) await 
expect(githubLinks).toBeVisible();
+      if ((await docLinks.count()) > 0) {
+        await expect(docLinks).toBeVisible();
+      }
+      if ((await communityLinks.count()) > 0) {
+        await expect(communityLinks).toBeVisible();
+      }
+      if ((await issuesLinks.count()) > 0) {
+        await expect(issuesLinks).toBeVisible();
+      }
+      if ((await githubLinks.count()) > 0) {
+        await expect(githubLinks).toBeVisible();
+      }
     });
 
     test('When navigating between home and login URLs, Then should maintain 
consistent user experience', async ({
       page
     }) => {
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
 
       const homeMetadata = await homePageUtil.getPageMetadata();
       expect(homeMetadata.path).toContain('#/');
       expect(homeMetadata.isAnonymous).toBe(true);
 
       await page.goto('/#/login', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await page.waitForURL(url => !url.toString().includes('#/login'));
 
       const loginMetadata = await homePageUtil.getPageMetadata();
@@ -149,7 +168,7 @@ test.describe('Anonymous User Login Redirect', () => {
     test('When multiple page loads occur on login URL, Then should 
consistently redirect to home', async ({ page }) => {
       for (let i = 0; i < 3; i++) {
         await page.goto('/#/login', { waitUntil: 'load' });
-        await zeppelinHelper.waitForZeppelinReady();
+        await waitForZeppelinReady(page);
         await waitForUrlNotContaining(page, '#/login');
 
         await expect(page.locator('h1', { hasText: 'Welcome to Zeppelin!' 
})).toBeVisible();
diff --git a/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts 
b/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
index 4dedf66218..9b8423dacd 100644
--- a/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
+++ b/zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
@@ -11,20 +11,21 @@
  */
 
 import { expect, test } from '@playwright/test';
-import { ZeppelinHelper } from '../../helper';
 import { ThemePage } from '../../models/theme.page';
-import { addPageAnnotationBeforeEach, PAGES } from '../../utils';
+import { addPageAnnotationBeforeEach, performLoginIfRequired, 
waitForZeppelinReady, PAGES } from '../../utils';
 
 test.describe('Dark Mode Theme Switching', () => {
   addPageAnnotationBeforeEach(PAGES.SHARE.THEME_TOGGLE);
-  let zeppelinHelper: ZeppelinHelper;
   let themePage: ThemePage;
 
   test.beforeEach(async ({ page }) => {
-    zeppelinHelper = new ZeppelinHelper(page);
     themePage = new ThemePage(page);
     await page.goto('/', { waitUntil: 'load' });
-    await zeppelinHelper.waitForZeppelinReady();
+    await waitForZeppelinReady(page);
+
+    // Handle authentication if shiro.ini exists
+    await performLoginIfRequired(page);
+
     // Ensure a clean localStorage for each test
     await themePage.clearLocalStorage();
   });
@@ -39,7 +40,7 @@ test.describe('Dark Mode Theme Switching', () => {
     await test.step('WHEN the user explicitly sets theme to light mode', async 
() => {
       await themePage.setThemeInLocalStorage('light');
       await page.reload();
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await themePage.assertLightTheme(); // Now it should be light mode with 
sun icon
     });
 
@@ -47,7 +48,7 @@ test.describe('Dark Mode Theme Switching', () => {
     await test.step('WHEN the user switches to dark mode', async () => {
       await themePage.setThemeInLocalStorage('dark');
       await page.reload();
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
     });
 
     // THEN: The theme changes to dark mode.
@@ -58,7 +59,7 @@ test.describe('Dark Mode Theme Switching', () => {
     // AND: User refreshes the page.
     await test.step('AND the user refreshes the page', async () => {
       await page.reload();
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
     });
 
     // THEN: Dark mode is maintained after refresh.
@@ -84,7 +85,7 @@ test.describe('Dark Mode Theme Switching', () => {
     await test.step('GIVEN: No localStorage, System preference is Light', 
async () => {
       await page.emulateMedia({ colorScheme: 'light' });
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       // When no explicit theme is set, it defaults to 'system' mode
       // Even in system mode with light preference, the icon should be robot
       await expect(themePage.rootElement).toHaveClass(/light/);
@@ -95,7 +96,7 @@ test.describe('Dark Mode Theme Switching', () => {
     await test.step('GIVEN: No localStorage, System preference is Dark 
(initial system state)', async () => {
       await themePage.setThemeInLocalStorage('system');
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await themePage.assertSystemTheme(); // Robot icon for system theme
     });
 
@@ -103,7 +104,7 @@ test.describe('Dark Mode Theme Switching', () => {
       await themePage.setThemeInLocalStorage('dark');
       await page.emulateMedia({ colorScheme: 'light' });
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await themePage.assertDarkTheme(); // localStorage should override system
     });
 
@@ -111,7 +112,7 @@ test.describe('Dark Mode Theme Switching', () => {
       await themePage.setThemeInLocalStorage('system');
       await page.emulateMedia({ colorScheme: 'light' });
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await expect(themePage.rootElement).toHaveClass(/light/);
       await expect(themePage.rootElement).toHaveAttribute('data-theme', 
'light');
       await themePage.assertSystemTheme(); // Robot icon for system theme
@@ -121,7 +122,7 @@ test.describe('Dark Mode Theme Switching', () => {
       await themePage.setThemeInLocalStorage('system');
       await page.emulateMedia({ colorScheme: 'dark' });
       await page.goto('/', { waitUntil: 'load' });
-      await zeppelinHelper.waitForZeppelinReady();
+      await waitForZeppelinReady(page);
       await expect(themePage.rootElement).toHaveClass(/dark/);
       await expect(themePage.rootElement).toHaveAttribute('data-theme', 
'dark');
       await themePage.assertSystemTheme(); // Robot icon for system theme
diff --git a/zeppelin-web-angular/e2e/utils.ts 
b/zeppelin-web-angular/e2e/utils.ts
index 652cedc53f..9f6193d083 100644
--- a/zeppelin-web-angular/e2e/utils.ts
+++ b/zeppelin-web-angular/e2e/utils.ts
@@ -10,7 +10,8 @@
  * limitations under the License.
  */
 
-import { test, TestInfo, Page } from '@playwright/test';
+import { test, Page, TestInfo } from '@playwright/test';
+import { LoginTestUtil } from './models/login-page.util';
 
 export const PAGES = {
   // Main App
@@ -159,3 +160,57 @@ export async function getBasicPageMetadata(
     path: getCurrentPath(page)
   };
 }
+
+export async function performLoginIfRequired(page: Page): Promise<boolean> {
+  const isShiroEnabled = await LoginTestUtil.isShiroEnabled();
+  if (!isShiroEnabled) {
+    return false;
+  }
+
+  const credentials = await LoginTestUtil.getTestCredentials();
+  const validUsers = Object.values(credentials).filter(
+    cred => cred.username && cred.password && cred.username !== 'INVALID_USER' 
&& cred.username !== 'EMPTY_CREDENTIALS'
+  );
+
+  if (validUsers.length === 0) {
+    return false;
+  }
+
+  const testUser = validUsers[0];
+
+  const isLoginVisible = await page.locator('zeppelin-login').isVisible();
+  if (isLoginVisible) {
+    const userNameInput = page.getByRole('textbox', { name: 'User Name' });
+    const passwordInput = page.getByRole('textbox', { name: 'Password' });
+    const loginButton = page.getByRole('button', { name: 'Login' });
+
+    await userNameInput.fill(testUser.username);
+    await passwordInput.fill(testUser.password);
+    await loginButton.click();
+
+    await page.waitForSelector('zeppelin-workspace', { timeout: 10000 });
+    return true;
+  }
+
+  return false;
+}
+
+export async function waitForZeppelinReady(page: Page): Promise<void> {
+  try {
+    await page.waitForLoadState('networkidle', { timeout: 30000 });
+    await page.waitForFunction(
+      () => {
+        const hasAngular = document.querySelector('[ng-version]') !== null;
+        const hasZeppelinContent =
+          document.body.textContent?.includes('Zeppelin') ||
+          document.body.textContent?.includes('Notebook') ||
+          document.body.textContent?.includes('Welcome');
+        const hasZeppelinRoot = document.querySelector('zeppelin-root') !== 
null;
+        return hasAngular && (hasZeppelinContent || hasZeppelinRoot);
+      },
+      { timeout: 60 * 1000 }
+    );
+  } catch (error) {
+    throw error instanceof Error ? error : new Error(`Zeppelin loading failed: 
${String(error)}`);
+  }
+}

Reply via email to