-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[ZEPPELIN-6358] Add E2E tests about /#/notebook/:noteid for New UI #5101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dididy
wants to merge
29
commits into
apache:master
Choose a base branch
from
dididy:e2e/notebook
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
fc8c105
Add notebook related tests
dididy 021d9dc
add retry for local test
dididy 7c539b8
combine tests
dididy d986379
add shortcut tests
dididy d029a96
fix broken tests
dididy 5f11492
remove unsued function and apply review
dididy b8dec42
add global teardown's cleanup test notebooks
dididy b87a077
cleanupTestNotebooks only for local, add waitForSelector
dididy d982b09
enhance tests
dididy 92c82d5
apply lint:fix
dididy ba26121
update playwright.config
dididy f03f36f
fix broken tests
dididy 9a95ad6
refactor error handling to fail fast instead of catching and continuing
dididy 2edc115
fix broken tests
dididy d875957
verify icon updates by asserting svg data-icon attribute
dididy 4f749b8
make verifyTitleEditingFunctionality assert actual editing behavior
dididy c56543c
lint fix + throw error
dididy e583521
extract browser-specific logic in executePlatformShortcut for clarity
dididy a3a1971
fix broken tests
dididy 558afed
fix broken tests
dididy c9c9c3b
add missing annotation
dididy a7f59f5
add additional tests
dididy fe5aba9
fix broken tests
dididy d058c1b
remove unused part by tbonelee
dididy 2d8be15
fix sidebar-functionality.spec related tests
dididy 40fb735
add sidebar aria-label
dididy 295f0c5
apply published-paragraph related tests
dididy 48c5cc5
apply review about notebook-paragraph and sidebar
dididy b4a6b7b
apply review rest of tests
dididy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| /* | ||
| * 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 { Locator, Page } from '@playwright/test'; | ||
| import { BasePage } from './base-page'; | ||
|
|
||
| export class FolderRenamePage extends BasePage { | ||
| readonly folderList: Locator; | ||
| readonly contextMenu: Locator; | ||
| readonly renameMenuItem: Locator; | ||
| readonly deleteMenuItem: Locator; | ||
| readonly moveToTrashMenuItem: Locator; | ||
| readonly renameModal: Locator; | ||
| readonly renameInput: Locator; | ||
| readonly confirmButton: Locator; | ||
| readonly cancelButton: Locator; | ||
| readonly validationError: Locator; | ||
| readonly deleteIcon: Locator; | ||
| readonly deleteConfirmation: Locator; | ||
| readonly deleteConfirmButton: Locator; | ||
| readonly deleteCancelButton: Locator; | ||
|
|
||
| constructor(page: Page) { | ||
| super(page); | ||
| this.folderList = page.locator('zeppelin-node-list'); | ||
| this.contextMenu = page.locator('.operation'); // Operation buttons area instead of dropdown | ||
| this.renameMenuItem = page.locator('a[nz-tooltip][nztooltiptitle="Rename folder"]').first(); | ||
| this.deleteMenuItem = page.locator('a[nz-tooltip][nztooltiptitle="Move folder to Trash"]').first(); | ||
| this.moveToTrashMenuItem = page.locator('a[nz-tooltip][nztooltiptitle="Move folder to Trash"]').first(); | ||
| this.renameModal = page.locator('.ant-modal'); | ||
| this.renameInput = page.locator('input[placeholder="Insert New Name"]'); | ||
| this.confirmButton = page.getByRole('button', { name: 'Rename' }); | ||
| this.cancelButton = page.locator('.ant-modal-close-x'); // Modal close button | ||
| this.validationError = page.locator( | ||
| '.ant-form-item-explain, .error-message, .validation-error, .ant-form-item-explain-error' | ||
| ); | ||
| this.deleteIcon = page.locator('i[nz-icon][nztype="delete"]'); | ||
| this.deleteConfirmation = page.locator('.ant-popover').filter({ hasText: 'This folder will be moved to trash.' }); | ||
| this.deleteConfirmButton = page.getByRole('button', { name: 'OK' }).last(); | ||
| this.deleteCancelButton = page.getByRole('button', { name: 'Cancel' }).last(); | ||
| } | ||
|
|
||
| async hoverOverFolder(folderName: string): Promise<void> { | ||
| // Wait for the folder list to be loaded | ||
| await this.folderList.waitFor({ state: 'visible' }); | ||
|
|
||
| // Find the folder node by locating the .node that contains the specific folder name | ||
| // Use a more reliable selector that targets the folder name exactly | ||
| const folderNode = this.page | ||
| .locator('.node') | ||
| .filter({ | ||
| has: this.page.locator('.folder .name', { hasText: folderName }) | ||
| }) | ||
| .first(); | ||
|
|
||
| // Wait for the folder to be visible and hover over the entire .node container | ||
| await folderNode.waitFor({ state: 'visible' }); | ||
| await folderNode.hover(); | ||
|
|
||
| // Wait for hover effects to take place by checking for interactive elements | ||
| await folderNode | ||
| .locator('a[nz-tooltip], i[nztype], button') | ||
| .first() | ||
| .waitFor({ | ||
| state: 'visible', | ||
| timeout: 2000 | ||
| }) | ||
| .catch(() => { | ||
| console.log('No interactive elements found after hover, continuing...'); | ||
| }); | ||
| } | ||
|
|
||
| async clickDeleteIcon(folderName: string): Promise<void> { | ||
| // First hover over the folder to reveal the delete icon | ||
| await this.hoverOverFolder(folderName); | ||
|
|
||
| // Find the specific folder node and its delete button | ||
| const folderNode = this.page | ||
| .locator('.node') | ||
| .filter({ | ||
| has: this.page.locator('.folder .name', { hasText: folderName }) | ||
| }) | ||
| .first(); | ||
|
|
||
| const deleteIcon = folderNode.locator('a[nz-tooltip][nztooltiptitle="Move folder to Trash"]'); | ||
| await deleteIcon.click(); | ||
| } | ||
|
|
||
| async clickRenameMenuItem(folderName: string): Promise<void> { | ||
| if (folderName) { | ||
| // Ensure the specific folder is hovered first | ||
| await this.hoverOverFolder(folderName); | ||
|
|
||
| // Find the specific folder node and its rename button | ||
| const folderNode = this.page | ||
| .locator('.node') | ||
| .filter({ | ||
| has: this.page.locator('.folder .name', { hasText: folderName }) | ||
| }) | ||
| .first(); | ||
|
|
||
| const renameIcon = folderNode.locator('a[nz-tooltip][nztooltiptitle="Rename folder"]'); | ||
| await renameIcon.click(); | ||
|
|
||
| // Wait for modal to appear by checking for its presence | ||
| await this.renameModal.waitFor({ state: 'visible', timeout: 3000 }); | ||
| } else { | ||
| // Fallback to generic rename button (now using .first() to avoid strict mode) | ||
| await this.renameMenuItem.click(); | ||
| await this.renameModal.waitFor({ state: 'visible', timeout: 3000 }); | ||
| } | ||
| } | ||
|
|
||
| async enterNewName(name: string): Promise<void> { | ||
| await this.renameInput.fill(name); | ||
| } | ||
|
|
||
| async clearNewName(): Promise<void> { | ||
| await this.renameInput.clear(); | ||
| } | ||
|
|
||
| async clickConfirm(): Promise<void> { | ||
| await this.confirmButton.click(); | ||
|
|
||
| // Wait for validation or submission to process by monitoring modal state | ||
| await this.page | ||
| .waitForFunction( | ||
| () => { | ||
| // Check if modal is still open or if validation errors appeared | ||
| const modal = document.querySelector('.ant-modal-wrap'); | ||
| const validationErrors = document.querySelectorAll('.ant-form-item-explain-error, .has-error'); | ||
|
|
||
| // If modal closed or validation errors appeared, processing is complete | ||
| return !modal || validationErrors.length > 0 || (modal && getComputedStyle(modal).display === 'none'); | ||
| }, | ||
| { timeout: 2000 } | ||
| ) | ||
| .catch(() => { | ||
| console.log('Modal state check timeout, continuing...'); | ||
| }); | ||
| } | ||
|
|
||
| async clickCancel(): Promise<void> { | ||
| await this.cancelButton.click(); | ||
| } | ||
|
|
||
| async isRenameModalVisible(): Promise<boolean> { | ||
| return this.renameModal.isVisible(); | ||
| } | ||
|
|
||
| async isFolderVisible(folderName: string): Promise<boolean> { | ||
| return this.page | ||
| .locator('.node') | ||
| .filter({ | ||
| has: this.page.locator('.folder .name', { hasText: folderName }) | ||
| }) | ||
| .first() | ||
| .isVisible(); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need authorizations to use Rest APIs. How about using test notebook directory?
zeppelin/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
Line 982 in 0a768bc
Maybe we could set
ZEPPELIN_NOTEBOOK_DIRenvironment variable, and let e2e script to use this to clean up test notebooks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to me that this approach would require restarting the server. Could you confirm if I understood that correctly? If so, it might feel a bit awkward. In the meantime, I’ll check whether a method that doesn’t rely on a REST API—such as using sockets—would be feasible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant was exporting a temporary directory for tests as an environment variable in
frontend.yml, and then deleting that directory all at once from somewhere.