Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions zeppelin-web-angular/.gitignore
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we'd better add a .gitignore file inside zeppelin-react directory to ignore dist and node_modules in zeppelin-react.

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
**/dist
/tmp
/out-tsc

# dependencies
/node_modules
**/node_modules

# profiling files
chrome-profiler-events.json
Expand Down
24 changes: 17 additions & 7 deletions zeppelin-web-angular/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@
},
"architect": {
"build": {
"builder": "ngx-build-plus:browser",
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"outputPath": "dist/zeppelin",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.json",
"customWebpackConfig": {
"path": "./webpack.config.js"
},
"assets": [
"src/favicon.ico",
"src/assets",
Expand All @@ -62,6 +65,11 @@
"glob": "**/*",
"input": "./WEB-INF",
"output": "/WEB-INF/"
},
{
"glob": "**/*",
"input": "./projects/zeppelin-react/dist",
"output": "/assets/react/"
}
],
"styles": [
Expand Down Expand Up @@ -100,27 +108,29 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"namedChunks": true,
"sourceMap": true
}
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "ngx-build-plus:dev-server",
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "zeppelin:build"
"browserTarget": "zeppelin:build",
"port": 4200,
"host": "localhost",
"liveReload": true,
"hmr": true
},
"configurations": {
"production": {
Expand Down
6 changes: 4 additions & 2 deletions zeppelin-web-angular/e2e/models/home-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ export class HomePage extends BasePage {
}

async navigateToHome(): Promise<void> {
await this.page.goto('/', { waitUntil: 'load' });
await this.page.goto('/');
await this.waitForPageLoad();
}

async navigateToLogin(): Promise<void> {
await this.page.goto('/#/login', { waitUntil: 'load' });
await this.page.goto('/#/login');
await this.waitForPageLoad();
// Wait for potential redirect to complete by checking URL change
await waitForUrlNotContaining(this.page, '#/login');
Expand Down Expand Up @@ -189,6 +189,8 @@ export class HomePage extends BasePage {
}

async filterNotes(searchTerm: string): Promise<void> {
await this.page.waitForLoadState('domcontentloaded', { timeout: 10000 });
await this.nodeList.filterInput.waitFor({ state: 'visible', timeout: 5000 });
await this.nodeList.filterInput.fill(searchTerm);
}

Expand Down
24 changes: 9 additions & 15 deletions zeppelin-web-angular/e2e/models/home-page.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class HomePageUtil {
await expect(this.homePage.notebookList).toBeVisible();

// Additional wait for content to load
await this.page.waitForTimeout(1000);
await this.page.waitForLoadState('networkidle', { timeout: 15000 });
}

async verifyNotebookRefreshFunctionality(): Promise<void> {
Expand Down Expand Up @@ -183,31 +183,25 @@ export class HomePageUtil {
async verifyCreateNewNoteWorkflow(): Promise<void> {
await this.homePage.clickCreateNewNote();

await this.page.waitForFunction(
() => {
return document.querySelector('zeppelin-note-create') !== null;
},
{ timeout: 10000 }
);
await this.page.waitForFunction(() => document.querySelector('zeppelin-note-create') !== null, { timeout: 10000 });
}

async verifyImportNoteWorkflow(): Promise<void> {
await this.homePage.clickImportNote();

await this.page.waitForFunction(
() => {
return document.querySelector('zeppelin-note-import') !== null;
},
{ timeout: 10000 }
);
await this.page.waitForFunction(() => document.querySelector('zeppelin-note-import') !== null, { timeout: 10000 });
}

async testFilterFunctionality(filterTerm: string): Promise<void> {
await this.page.waitForLoadState('networkidle', { timeout: 10000 });
await this.homePage.filterNotes(filterTerm);

await this.page.waitForTimeout(1000);
const notebookTreeLocator = this.page.locator('nz-tree .node');

const filteredResults = await this.page.locator('nz-tree .node').count();
await notebookTreeLocator.first().waitFor({ state: 'attached', timeout: 15000 });
await expect(notebookTreeLocator.first()).toBeVisible({ timeout: 20000 });

const filteredResults = await notebookTreeLocator.count();
expect(filteredResults).toBeGreaterThanOrEqual(0);
}

Expand Down
2 changes: 1 addition & 1 deletion zeppelin-web-angular/e2e/models/workspace-page.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export class WorkspaceTestUtil {

async navigateAndWaitForLoad(): Promise<void> {
await this.workspacePage.navigateToWorkspace();
await waitForZeppelinReady(this.page);
await performLoginIfRequired(this.page);
await waitForZeppelinReady(this.page);
}

async verifyWorkspaceLayout(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ test.describe('Home Page Note Operations', () => {
await page.goto('/');
await waitForZeppelinReady(page);
await performLoginIfRequired(page);
await page.waitForSelector('zeppelin-node-list', { timeout: 15000 });
const noteListLocator = page.locator('zeppelin-node-list');
await expect(noteListLocator).toBeVisible({ timeout: 15000 });
});

test.describe('Given note operations are available', () => {
Expand Down Expand Up @@ -93,13 +94,10 @@ test.describe('Home Page Note Operations', () => {

await page
.waitForFunction(
() => {
return (
document.querySelector('zeppelin-note-rename') !== null ||
document.querySelector('[role="dialog"]') !== null ||
document.querySelector('.ant-modal') !== null
);
},
() =>
document.querySelector('zeppelin-note-rename') !== null ||
document.querySelector('[role="dialog"]') !== null ||
document.querySelector('.ant-modal') !== null,
{ timeout: 5000 }
)
.catch(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ test.describe('Published Paragraph', () => {
timeout: 10000
});
});

test('should enter published paragraph in React mode via URL with react=true', async ({ page }) => {
await test.step('Given I navigate to React mode URL', async () => {
const reactModeUrl = `/#/notebook/${testNotebook.noteId}/paragraph/${testNotebook.paragraphId}?react=true`;
await page.goto(reactModeUrl);
await waitForZeppelinReady(page);

await page.waitForURL(`**/${testNotebook.noteId}/paragraph/${testNotebook.paragraphId}*`, {
timeout: 15000
});
});

await test.step('Then React mode should be active', async () => {
const currentUrl = page.url();
expect(currentUrl).toContain('react=true');
});
});
Comment on lines +103 to +118
Copy link
Contributor

Choose a reason for hiding this comment

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

If there's no special handling in place, navigating to a URL that includes query parameters will always result in those parameters being present in the final URL. In that case, wouldn't this test always pass regardless of whether React mode is actually active?

It may be better to either remove this test or revise it to validate React mode using a different, more reliable indicator.

});

test('should show confirmation modal and allow running the paragraph', async ({ page }) => {
Expand All @@ -122,7 +139,7 @@ test.describe('Published Paragraph', () => {
await publishedParagraphPage.navigateToPublishedParagraph(noteId, paragraphId);

const modal = publishedParagraphPage.confirmationModal;
await expect(modal).toBeVisible();
await expect(modal).toBeVisible({ timeout: 300000 });

// Check for the new enhanced modal content
await expect(publishedParagraphPage.modalTitle).toHaveText('Run Paragraph?');
Expand All @@ -138,4 +155,53 @@ test.describe('Published Paragraph', () => {
await runButton.click();
await expect(modal).toBeHidden();
});

test('should show confirmation modal in React mode and allow running the paragraph', async ({ page }) => {
const { noteId, paragraphId } = testNotebook;

await test.step('Given I clear paragraph output in normal notebook view', async () => {
await publishedParagraphPage.navigateToNotebook(noteId);

const paragraphElement = page.locator('zeppelin-notebook-paragraph').first();
const paragraphResult = paragraphElement.locator('zeppelin-notebook-paragraph-result');

// Only clear output if result exists
if (await paragraphResult.isVisible()) {
const settingsButton = paragraphElement.locator('a[nz-dropdown]');
await settingsButton.click();

const clearOutputButton = page.locator('li.list-item:has-text("Clear output")');
await clearOutputButton.click();
await expect(paragraphResult).toBeHidden();
}
Comment on lines +168 to +176
Copy link
Contributor

Choose a reason for hiding this comment

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

I think test scenarios should be as deterministic as possible.
Since beforeEach creates a new notebook, we may not need a conditional if block here to clear the output.
It should be sufficient to assert that the paragraph result is hidden with something like expect(paragraphResult).toBeHidden().

});

await test.step('When I navigate to React mode published paragraph URL', async () => {
const reactModeUrl = `/#/notebook/${noteId}/paragraph/${paragraphId}?react=true`;
await page.goto(reactModeUrl);
await waitForZeppelinReady(page);

// Wait for React mode to load
await page.waitForTimeout(2000);
});

await test.step('Then confirmation modal should appear in React mode', async () => {
const modal = publishedParagraphPage.confirmationModal;
await expect(modal).toBeVisible({ timeout: 30000 });

// Check for the enhanced modal content
await expect(publishedParagraphPage.modalTitle).toHaveText('Run Paragraph?');

// Verify that the modal shows code preview
const modalContent = publishedParagraphPage.confirmationModal.locator('.ant-modal-confirm-content');
await expect(modalContent).toContainText('This paragraph contains the following code:');
await expect(modalContent).toContainText('Would you like to execute this code?');

// Click the Run button in the modal (OK button in confirmation modal)
const runButton = modal.locator('.ant-modal-confirm-btns .ant-btn-primary');
await expect(runButton).toBeVisible();
await runButton.click();
await expect(modal).toBeHidden();
});
});
});
40 changes: 17 additions & 23 deletions zeppelin-web-angular/e2e/tests/theme/dark-mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ test.describe('Dark Mode Theme Switching', () => {
await themePage.clearLocalStorage();
});

test('Scenario: User can switch to dark mode and persistence is maintained', async ({ page, context }) => {
let currentPage = page;

test('Scenario: User can switch to dark mode and persistence is maintained', async ({ page, browserName }) => {
// GIVEN: User is on the main page, which starts in 'system' mode by default (localStorage cleared).
await test.step('GIVEN the page starts in system mode', async () => {
await themePage.assertSystemTheme(); // Robot icon for system theme
Expand All @@ -41,32 +39,28 @@ test.describe('Dark Mode Theme Switching', () => {
// WHEN: Explicitly set theme to light mode for the rest of the test.
await test.step('WHEN the user explicitly sets theme to light mode', async () => {
await themePage.setThemeInLocalStorage('light');
await page.reload();
await page.waitForTimeout(500);
if (browserName === 'webkit') {
const currentUrl = page.url();
await page.goto(currentUrl, { waitUntil: 'load' });
} else {
page.reload();
}
await waitForZeppelinReady(page);
await themePage.assertLightTheme(); // Now it should be light mode with sun icon
});

// WHEN: User switches to dark mode by setting localStorage and reloading.
await test.step('WHEN the user switches to dark mode', async () => {
await test.step('WHEN the user explicitly sets theme to dark mode', async () => {
await themePage.setThemeInLocalStorage('dark');
const newPage = await context.newPage();
await newPage.goto(currentPage.url());
await waitForZeppelinReady(newPage);

// Update themePage to use newPage and verify dark mode
themePage = new ThemePage(newPage);
currentPage = newPage;
await themePage.assertDarkTheme();
});

// AND: User refreshes the page.
await test.step('AND the user refreshes the page', async () => {
await currentPage.reload();
await waitForZeppelinReady(currentPage);
});

// THEN: Dark mode is maintained after refresh.
await test.step('THEN dark mode is maintained after refresh', async () => {
await page.waitForTimeout(500);
if (browserName === 'webkit') {
const currentUrl = page.url();
await page.goto(currentUrl, { waitUntil: 'load' });
} else {
page.reload();
}
await waitForZeppelinReady(page);
await themePage.assertDarkTheme();
});

Expand Down
4 changes: 3 additions & 1 deletion zeppelin-web-angular/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ export function getCoverageTransformPaths(): string[] {
}

export async function waitForUrlNotContaining(page: Page, fragment: string) {
await page.waitForURL(url => !url.toString().includes(fragment));
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
await page.waitForURL(url => !url.toString().includes(fragment), { timeout: 15000 });
}

export function getCurrentPath(page: Page): string {
Expand Down Expand Up @@ -183,6 +184,7 @@ export async function performLoginIfRequired(page: Page): Promise<boolean> {
await loginButton.click();

await page.waitForSelector('text=Welcome to Zeppelin!', { timeout: 5000 });
await page.waitForLoadState('networkidle');
return true;
}

Expand Down
Loading
Loading