Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
134 commits
Select commit Hold shift + click to select a range
3e8ce73
change playwright config ts to js
dididy Dec 6, 2025
25992d9
change to folder base note managing and cleanup
dididy Dec 6, 2025
fc44d9c
add notebook related methods in utils
dididy Dec 6, 2025
ce0712c
add comment for globl-teardown
dididy Dec 6, 2025
3f69895
remove unnecessary code
dididy Dec 6, 2025
2f57b0d
utils convert to arrow func, separate regex to constant
dididy Dec 6, 2025
5cbb05d
Add notebook related tests
dididy Oct 9, 2025
e00c303
combine tests
dididy Oct 10, 2025
35d1842
add shortcut tests
dididy Oct 10, 2025
8b5f2e1
fix broken tests
dididy Oct 14, 2025
428de97
remove unsued function and apply review
dididy Oct 19, 2025
e5c9efb
add global teardown's cleanup test notebooks
dididy Oct 24, 2025
746e1e8
cleanupTestNotebooks only for local, add waitForSelector
dididy Oct 24, 2025
dff6451
enhance tests
dididy Oct 24, 2025
f399ed2
apply lint:fix
dididy Oct 26, 2025
d6b763f
fix broken tests
dididy Oct 27, 2025
8d92b37
refactor error handling to fail fast instead of catching and continuing
dididy Dec 2, 2025
1871327
fix broken tests
dididy Oct 27, 2025
b18ae4d
verify icon updates by asserting svg data-icon attribute
dididy Oct 27, 2025
c407b99
make verifyTitleEditingFunctionality assert actual editing behavior
dididy Oct 27, 2025
5626dc3
lint fix + throw error
dididy Oct 27, 2025
ff7a9d7
extract browser-specific logic in executePlatformShortcut for clarity
dididy Oct 27, 2025
a3793c7
fix broken tests
dididy Oct 27, 2025
8c24df1
fix broken tests
dididy Oct 30, 2025
1f39a48
add missing annotation
dididy Oct 31, 2025
e38875d
add additional tests
dididy Nov 1, 2025
a1b1a24
fix broken tests
dididy Nov 4, 2025
76c9d17
remove unused part by tbonelee
dididy Nov 7, 2025
d7ecc40
fix sidebar-functionality.spec related tests
dididy Nov 7, 2025
33da679
add sidebar aria-label
dididy Nov 7, 2025
2db8b5f
apply published-paragraph related tests
dididy Nov 7, 2025
f33d40e
apply review about notebook-paragraph and sidebar
dididy Nov 7, 2025
5082747
apply review rest of tests
dididy Nov 7, 2025
dc36067
fix action bar relate tests
dididy Nov 8, 2025
01c2b32
fix published paragraph test
dididy Nov 8, 2025
947be5b
fix notebook paragraph related tests
dididy Nov 9, 2025
e04c735
fix tests
dididy Nov 9, 2025
d071569
fix broken tests
dididy Nov 9, 2025
5ba3e17
add tear up and tear down related about ZEPPELIN_NOTEBOOK_DIR
dididy Nov 11, 2025
8ae424b
fix borken test
dididy Nov 12, 2025
f59666a
fix test pom about keyboard and sidebar
dididy Nov 14, 2025
8a4831a
remove try catch
dididy Nov 14, 2025
ceb3406
remove conditional test
dididy Nov 14, 2025
b239020
remove try catch
dididy Nov 14, 2025
f7fd948
fix broken tests
dididy Nov 14, 2025
ab04607
fix remove condition of test
dididy Nov 15, 2025
f6e61a2
fix broken tests
dididy Nov 15, 2025
53bb844
fix broken test
dididy Nov 18, 2025
f7567a7
add ci settings for python
dididy Nov 19, 2025
59949e8
remove conditional skip tests
dididy Nov 19, 2025
023af3c
fix interpreter not found error
dididy Nov 19, 2025
4ebe9a5
fix broken tests
dididy Nov 20, 2025
5cece0b
fix issue where Control+Alt+F dialog does not appear
dididy Nov 21, 2025
59e7907
fix broken tests
dididy Nov 24, 2025
94198f5
conver to use createTestNotebook deleteTestNotebook from utils
dididy Nov 25, 2025
283de97
fix broken tests
dididy Nov 25, 2025
8cb1e8c
disable the button in the rename modal when the input field is empty
dididy Nov 26, 2025
cc94601
fix broken tests
dididy Nov 26, 2025
fd9aed0
test
dididy Nov 28, 2025
f6a52fa
Apply minor suggestions from code review
tbonelee Nov 28, 2025
89ad9f9
Apply minor suggestions from code review
tbonelee Nov 28, 2025
f32e043
Remove console log
tbonelee Nov 28, 2025
7d25cf7
Remove unused method
tbonelee Nov 28, 2025
d7821cf
Reformat blank lines
tbonelee Nov 28, 2025
fedc862
Reformat blank lines
tbonelee Nov 28, 2025
10f2568
fix broken tests
dididy Nov 28, 2025
d655de7
change goto('/') to goto('/#/')
dididy Dec 2, 2025
e64c0ab
fix borken tests
dididy Nov 29, 2025
ba559d8
remove r setup in frontend.yml playwright step
dididy Nov 30, 2025
7c244a5
reflecting a simple review
dididy Nov 30, 2025
ef32970
fix broken tests
dididy Dec 1, 2025
5fc96b5
added a comment to the additionally assigned issue.
dididy Dec 1, 2025
0b51039
fix broken test
dididy Dec 2, 2025
2c08fd6
remove deleteNotebook func and step for afterEach
dididy Dec 2, 2025
acb8fb9
refactor teardown step about delete note
dididy Dec 2, 2025
e03ce58
refactor tests
dididy Dec 5, 2025
0d79ff4
fix broken tests
dididy Dec 6, 2025
61cc9be
refactoring
dididy Dec 6, 2025
301fd76
fix broken tests
dididy Dec 6, 2025
6a2cb02
refactoring and fix broken tests
dididy Dec 6, 2025
6bb1a7e
fix broken tests
dididy Dec 6, 2025
6125729
fix borken tests
dididy Dec 7, 2025
a060732
reset unnecessary changes about package.json
dididy Dec 7, 2025
fa0466d
add safe step for watiForNotebookLinks
dididy Dec 7, 2025
8144699
fix broken tests
dididy Dec 7, 2025
11ecb43
change e2eTestFolder selector on base-page
dididy Dec 7, 2025
6daa110
refactoring performLoginIfRequired for broken tests
dididy Dec 7, 2025
3fb9315
fix broken tests
dididy Dec 7, 2025
bcc79ea
add expandfolder step in getFolderNode
dididy Dec 7, 2025
6ca454d
fix broken test
dididy Dec 7, 2025
36e8dd9
fix broken test about Control+Alt+F
dididy Dec 7, 2025
9c84a38
fix broken test about rename folder
dididy Dec 8, 2025
52b563a
fix broken test
dididy Dec 8, 2025
832e8e5
move constants location and make a new constant
dididy Dec 8, 2025
058776d
add comments for ZEPPELIN_E2E_TEST_NOTEBOOK_DIR
dididy Dec 8, 2025
1536005
simplize Setup Test Notebook Directory step in CI
dididy Dec 8, 2025
b997b44
separate constatns to constant file
dididy Dec 8, 2025
a02cf95
rollback constants location for circular dependency issue
dididy Dec 8, 2025
7f3d117
fix broken test
dididy Dec 8, 2025
fc218c6
fix broken test
dididy Dec 9, 2025
b0658f6
fix env config in yml
dididy Dec 9, 2025
b72afac
fix path condition in cleanupTestNotebooks
dididy Dec 9, 2025
9158f07
fix broken test
dididy Dec 9, 2025
df5c72f
add waitForZeppelinReady step in performLoginIfRequired
dididy Dec 10, 2025
cecf9fa
fix broken test
dididy Dec 10, 2025
894bb56
fix broken test
dididy Dec 10, 2025
4937c35
fix broken test
dididy Dec 10, 2025
a54fbd0
fix broken test
dididy Dec 10, 2025
5d6e07f
fix broken test
dididy Dec 10, 2025
062836b
fix broken test
dididy Dec 11, 2025
a654de2
fix broken test
dididy Dec 11, 2025
17cd586
fix broken test
dididy Dec 11, 2025
f8bebc1
fix broken test
dididy Dec 11, 2025
6b0121b
fix broken test
dididy Dec 12, 2025
33d6d59
fix broken test
dididy Dec 12, 2025
0a8d223
fix constants path
dididy Dec 14, 2025
cf67a1b
fix broken test
dididy Dec 14, 2025
fda5987
updated the folder rename test to run outside of E2E_TEST_FOLDER for …
dididy Dec 15, 2025
7bd0d0d
Ctrl+Y marked as skipped in CI - ZEPPELIN-6379
dididy Dec 15, 2025
8bf4f78
apply review
dididy Dec 16, 2025
74a7eee
apply review
dididy Dec 17, 2025
5c30df9
fix broken tests
dididy Dec 18, 2025
1a77f06
fix broken test
dididy Dec 18, 2025
57895fe
fix broken test
dididy Dec 18, 2025
6cdce4d
fix broken test
dididy Dec 19, 2025
632d0dd
fix broken test
dididy Dec 19, 2025
611ed3c
fix broken test
dididy Dec 19, 2025
e0e99b9
fix broken test
dididy Dec 19, 2025
5ded275
apply review
dididy Dec 19, 2025
8c3dab5
apply review
dididy Dec 20, 2025
6010891
remove unused code
dididy Dec 20, 2025
450cb9c
refactoring tests
dididy Dec 20, 2025
0c97624
fix broken test
dididy Dec 22, 2025
29c9a2e
revert unproper change for selenium ci step
dididy Dec 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ env:
SPARK_LOCAL_IP: 127.0.0.1
ZEPPELIN_LOCAL_IP: 127.0.0.1
INTERPRETERS: '!hbase,!jdbc,!file,!flink,!cassandra,!elasticsearch,!bigquery,!alluxio,!livy,!groovy,!java,!neo4j,!sparql,!mongodb'
ZEPPELIN_E2E_TEST_NOTEBOOK_DIR: '/tmp/zeppelin-e2e-notebooks'

permissions:
contents: read # to fetch code (actions/checkout)
Expand Down Expand Up @@ -62,9 +63,13 @@ jobs:

run-playwright-e2e-tests:
runs-on: ubuntu-24.04
env:
# Use VFS storage instead of Git to avoid Git-related issues in CI
ZEPPELIN_NOTEBOOK_STORAGE: org.apache.zeppelin.notebook.repo.VFSNotebookRepo
strategy:
matrix:
mode: [anonymous, auth]
python: [ 3.9 ]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -93,15 +98,29 @@ jobs:
key: ${{ runner.os }}-zeppelin-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-zeppelin-
- name: Setup conda environment with python ${{ matrix.python }}
uses: conda-incubator/setup-miniconda@v3
with:
activate-environment: python_only
python-version: ${{ matrix.python }}
auto-activate-base: false
use-mamba: true
channels: conda-forge,defaults
channel-priority: strict
- name: Install application
run: ./mvnw clean install -DskipTests -am -pl zeppelin-web-angular ${MAVEN_ARGS}
run: ./mvnw clean install -DskipTests -am -pl python,rlang,zeppelin-jupyter-interpreter,zeppelin-web-angular ${MAVEN_ARGS}
- name: Setup Zeppelin Server (Shiro.ini)
run: |
export ZEPPELIN_CONF_DIR=./conf
if [ "${{ matrix.mode }}" != "anonymous" ]; then
cp conf/shiro.ini.template conf/shiro.ini
sed -i 's/user1 = password2, role1, role2/user1 = password2, role1, role2, admin/' conf/shiro.ini
fi
- name: Setup Test Notebook Directory
run: |
# NOTE: Must match zeppelin.notebook.dir defined in pom.xml
mkdir -p $ZEPPELIN_E2E_TEST_NOTEBOOK_DIR
echo "Created test notebook directory: $ZEPPELIN_E2E_TEST_NOTEBOOK_DIR"
- name: Run headless E2E test with Maven
run: xvfb-run --auto-servernum --server-args="-screen 0 1024x768x24" ./mvnw verify -pl zeppelin-web-angular -Pweb-e2e ${MAVEN_ARGS}
- name: Upload Playwright Report
Expand All @@ -110,10 +129,20 @@ jobs:
with:
name: playwright-report-${{ matrix.mode }}
path: zeppelin-web-angular/playwright-report/
retention-days: 30
retention-days: 3
- name: Print Zeppelin logs
if: always()
run: if [ -d "logs" ]; then cat logs/*; fi
- name: Cleanup Test Notebook Directory
if: always()
run: |
if [ -d "$ZEPPELIN_E2E_TEST_NOTEBOOK_DIR" ]; then
echo "Cleaning up test notebook directory: $ZEPPELIN_E2E_TEST_NOTEBOOK_DIR"
rm -rf $ZEPPELIN_E2E_TEST_NOTEBOOK_DIR
echo "Test notebook directory cleaned up"
else
echo "No test notebook directory to clean up"
fi

test-selenium-with-spark-module-for-spark-3-5:
runs-on: ubuntu-24.04
Expand Down
11 changes: 11 additions & 0 deletions zeppelin-web-angular/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@
"yoda": "error"
}
},
{
"files": ["*.js"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"env": {
"node": true,
"es6": true
}
},
{
"files": ["*.html"],
"extends": ["plugin:@angular-eslint/template/recommended"],
Expand Down
90 changes: 90 additions & 0 deletions zeppelin-web-angular/e2e/cleanup-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 { BASE_URL, E2E_TEST_FOLDER } from './models/base-page';

export const cleanupTestNotebooks = async () => {
try {
console.log('Cleaning up test folder via API...');

// Get all notebooks and folders
const response = await fetch(`${BASE_URL}/api/notebook`);
const data = await response.json();
if (!data.body || !Array.isArray(data.body)) {
console.log('No notebooks found or invalid response format');
return;
}

// Find the test folders (E2E_TEST_FOLDER, TestFolder_, and TestFolderRenamed_ patterns)
const testFolders = data.body.filter((item: { path: string }) => {
if (!item.path || item.path.includes(`~Trash`)) {
return false;
}
const folderName = item.path.split('/')[1];
return (
folderName === E2E_TEST_FOLDER ||
folderName?.startsWith('TestFolder_') ||
folderName?.startsWith('TestFolderRenamed_')
);
});

if (testFolders.length === 0) {
console.log('No test folder found to clean up');
return;
}

await Promise.all(
testFolders.map(async (testFolder: { id: string; path: string }) => {
try {
console.log(`Deleting test folder: ${testFolder.id} (${testFolder.path})`);

const deleteResponse = await fetch(`${BASE_URL}/api/notebook/${testFolder.id}`, {
method: 'DELETE'
});

// Although a 500 status code is generally not considered a successful response,
// this API returns 500 even when the operation actually succeeds.
// I'll investigate this further and create an issue.
if (deleteResponse.status === 200 || deleteResponse.status === 500) {
console.log(`Deleted test folder: ${testFolder.path}`);
} else {
console.warn(`Failed to delete test folder ${testFolder.path}: ${deleteResponse.status}`);
}
} catch (error) {
console.error(`Error deleting test folder ${testFolder.path}:`, error);
}
})
);

console.log('Test folder cleanup completed');
} catch (error) {
if (error instanceof Error && error.message.includes('ECONNREFUSED')) {
console.error('Failed to connect to local server. Please start the frontend server first:');
console.error(' npm start');
console.error(` or make sure ${BASE_URL} is running`);
} else {
console.warn('Failed to cleanup test folder:', error);
}
}
};

if (require.main === module) {
cleanupTestNotebooks()
.then(() => {
console.log('Cleanup completed successfully');
process.exit(0);
})
.catch(error => {
console.error('Cleanup failed:', error);
process.exit(1);
});
}
26 changes: 25 additions & 1 deletion zeppelin-web-angular/e2e/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
* limitations under the License.
*/

import * as fs from 'fs';
import { LoginTestUtil } from './models/login-page.util';

async function globalSetup() {
console.log('🔧 Global Setup: Checking Shiro configuration...');
console.log('Global Setup: Preparing test environment...');

// Reset cache to ensure fresh check
LoginTestUtil.resetCache();

// Set up test notebook directory if specified
await setupTestNotebookDirectory();

// Check Shiro configuration
const isShiroEnabled = await LoginTestUtil.isShiroEnabled();

if (isShiroEnabled) {
Expand All @@ -33,4 +38,23 @@ async function globalSetup() {
}
}

async function setupTestNotebookDirectory(): Promise<void> {
const testNotebookDir = process.env.ZEPPELIN_E2E_TEST_NOTEBOOK_DIR;

if (!testNotebookDir) {
console.log('No custom test notebook directory configured');
return;
}

console.log(`Setting up test notebook directory: ${testNotebookDir}`);

// Remove existing directory if it exists, then create fresh
if (fs.existsSync(testNotebookDir)) {
await fs.promises.rmdir(testNotebookDir, { recursive: true });
}

fs.mkdirSync(testNotebookDir, { recursive: true });
fs.chmodSync(testNotebookDir, 0o777);
}

export default globalSetup;
32 changes: 28 additions & 4 deletions zeppelin-web-angular/e2e/global-teardown.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

We could use ZEPPELIN_E2E_TEST_NOTEBOOK_DIR to remove test notebook directory if possible.

Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,37 @@
* limitations under the License.
*/

import { exec } from 'child_process';
import { promisify } from 'util';
import { LoginTestUtil } from './models/login-page.util';

async function globalTeardown() {
console.log('🧹 Global Teardown: Cleaning up test environment...');
const execAsync = promisify(exec);

const globalTeardown = async () => {
console.log('Global Teardown: Cleaning up test environment...');

LoginTestUtil.resetCache();
console.log('✅ Test cache cleared');
}
console.log('Test cache cleared');

// CI: Uses ZEPPELIN_E2E_TEST_NOTEBOOK_DIR which gets cleaned up by workflow
// Local: Uses API-based cleanup to avoid server restart required for directory changes
if (!process.env.CI) {
console.log('Running cleanup script: npx tsx e2e/cleanup-util.ts');

try {
// The reason for calling it this way instead of using the function directly
// is to maintain compatibility between ESM and CommonJS modules.
const { stdout, stderr } = await execAsync('npx tsx e2e/cleanup-util.ts');
if (stdout) {
console.log(stdout);
}
if (stderr) {
console.error(stderr);
}
} catch (error) {
console.error('Cleanup script failed:', error);
}
}
};

export default globalTeardown;
50 changes: 42 additions & 8 deletions zeppelin-web-angular/e2e/models/base-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,55 @@

import { Locator, Page } from '@playwright/test';

export const E2E_TEST_FOLDER = 'E2E_TEST_FOLDER';
export const BASE_URL = 'http://localhost:4200';

export class BasePage {
readonly page: Page;
readonly loadingScreen: Locator;

readonly zeppelinNodeList: Locator;
readonly zeppelinWorkspace: Locator;
readonly zeppelinPageHeader: Locator;
readonly zeppelinHeader: Locator;

constructor(page: Page) {
this.page = page;
this.loadingScreen = page.locator('.spin-text');
this.zeppelinNodeList = page.locator('zeppelin-node-list');
this.zeppelinWorkspace = page.locator('zeppelin-workspace');
this.zeppelinPageHeader = page.locator('zeppelin-page-header');
this.zeppelinHeader = page.locator('zeppelin-header');
}

async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState('domcontentloaded');
try {
await this.loadingScreen.waitFor({ state: 'hidden', timeout: 5000 });
} catch {
console.log('Loading screen not found');
}
await this.page.waitForLoadState('domcontentloaded', { timeout: 15000 });
}

async navigateToRoute(
route: string,
options?: { timeout?: number; waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' }
): Promise<void> {
await this.page.goto(`/#${route}`, {
waitUntil: 'domcontentloaded',
timeout: 60000,
...options
});
await this.waitForPageLoad();
}

async navigateToHome(): Promise<void> {
await this.navigateToRoute('/');
}

getCurrentPath(): string {
const url = new URL(this.page.url());
return url.hash || url.pathname;
}

async waitForUrlNotContaining(fragment: string): Promise<void> {
await this.page.waitForURL(url => !url.toString().includes(fragment));
}

async getElementText(locator: Locator): Promise<string> {
return (await locator.textContent()) || '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,36 @@
*/

import { expect, Locator, Page } from '@playwright/test';
import { BasePage } from './base-page';

export class ThemePage {
readonly page: Page;
export class DarkModePage extends BasePage {
readonly themeToggleButton: Locator;
readonly rootElement: Locator;

constructor(page: Page) {
this.page = page;
super(page);
this.themeToggleButton = page.locator('zeppelin-theme-toggle button');
this.rootElement = page.locator('html');
}

async toggleTheme() {
await this.themeToggleButton.click();
await this.themeToggleButton.click({ timeout: 15000 });
}

async assertDarkTheme() {
await expect(this.rootElement).toHaveClass(/dark/);
await expect(this.rootElement).toHaveClass(/dark/, { timeout: 10000 });
await expect(this.rootElement).toHaveAttribute('data-theme', 'dark');
await expect(this.themeToggleButton).toHaveText('dark_mode');
}

async assertLightTheme() {
await expect(this.rootElement).toHaveClass(/light/);
await expect(this.rootElement).toHaveClass(/light/, { timeout: 10000 });
await expect(this.rootElement).toHaveAttribute('data-theme', 'light');
await expect(this.themeToggleButton).toHaveText('light_mode');
}

async assertSystemTheme() {
await expect(this.themeToggleButton).toHaveText('smart_toy');
await expect(this.themeToggleButton).toHaveText('smart_toy', { timeout: 60000 });
}

async setThemeInLocalStorage(theme: 'light' | 'dark' | 'system') {
Expand Down
Loading
Loading