-
Notifications
You must be signed in to change notification settings - Fork 39
Automate tests for LCLS on xml #610
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
Merged
Merged
Changes from 11 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
3a91f49
diagnostic, type ahead, quickfix checks
90e48be
timeout debug
47470cd
refocus editor
f7d685c
more debug
917f815
editor tab focus
f97aff6
better waits
e17d5e8
changed from data source to logging for LCLS test
e45a415
correct #391
1201415
last test
73b25d0
removed stray a
0eb2688
more concise tests
05dd42d
PR changes
81e47dc
Merge remote-tracking branch 'origin/main' into automate-xml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,351 @@ | ||
| /* | ||
| * IBM Confidential | ||
| * Copyright IBM Corp. 2025, 2026 | ||
| */ | ||
| import { expect } from 'chai'; | ||
| import { By, EditorView, TextEditor, VSBrowser, WebDriver, Workbench, BottomBarPanel, MarkerType, Key } from 'vscode-extension-tester'; | ||
| import * as utils from './utils/testUtils'; | ||
| import { logger } from './utils/testLogger'; | ||
| import * as path from 'path'; | ||
|
|
||
| describe('Liberty Config Language Server Tests for Maven Project', () => { | ||
| let editorView: EditorView; | ||
| let editor: TextEditor; | ||
| let wait: any; | ||
| let driver: WebDriver; | ||
| let originalContent: string; | ||
|
|
||
| before(async function() { | ||
| this.timeout(60000); | ||
| driver = VSBrowser.instance.driver; | ||
| wait = utils.getWaitHelper(); | ||
|
|
||
| // Open workspace | ||
| await VSBrowser.instance.openResources(utils.getMvnProjectPath()); | ||
| await VSBrowser.instance.waitForWorkbench(); | ||
| editorView = new EditorView(); | ||
|
|
||
| // Open the real server.xml | ||
| const serverXmlPath = path.resolve( | ||
| utils.getMvnProjectPath(), | ||
| 'src', 'main', 'liberty', 'config', 'server.xml' | ||
| ); | ||
|
|
||
| await VSBrowser.instance.openResources(serverXmlPath, async () => { | ||
| await wait.sleep(3000); | ||
| }); | ||
|
|
||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
| originalContent = await editor.getText(); | ||
| logger.info('Server.xml file opened and original content saved'); | ||
| }); | ||
|
|
||
| afterEach(async function() { | ||
| this.timeout(30000); | ||
| if (this.currentTest?.state === 'failed') { | ||
| await driver.takeScreenshot(); | ||
| logger.error(`Test failed: ${this.currentTest?.title}`); | ||
| } | ||
|
|
||
| // Close the bottom bar and re-focus the editor | ||
| await new BottomBarPanel().toggle(false); | ||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
| // Wait until getText() returns the actual server.xml content | ||
| await utils.waitForCondition(async () => { | ||
| const text = await editor.getText(); | ||
| return text.includes('<server') ? true : undefined; | ||
| }, 15); | ||
|
|
||
| // Restore original content | ||
| if (originalContent) { | ||
| await editor.setText(originalContent); | ||
| await editor.save(); | ||
| await wait.sleep(3000); | ||
| logger.info('Restored original server.xml content'); | ||
| } | ||
| }); | ||
|
|
||
| after(async function() { | ||
| this.timeout(30000); | ||
| try { | ||
| await new Workbench().executeCommand('revert file'); | ||
| await wait.sleep(500); | ||
| await editorView.closeAllEditors(); | ||
| logger.info('Closed all editors'); | ||
| } catch (error) { | ||
| logger.error('Failed to close editors', error); | ||
| } | ||
| utils.copyScreenshotsToProjectFolder('maven'); | ||
| }); | ||
|
|
||
| it('Liberty Language Server should initialize', async function() { | ||
| this.timeout(60000); | ||
| logger.testStart('Liberty Language Server should initialize'); | ||
| await utils.waitForLanguageServerInit( | ||
| 'Language Support for Liberty', | ||
| 'Initialized Liberty Language server', | ||
| 60 | ||
| ); | ||
| logger.testComplete('Liberty Language Server initialized successfully'); | ||
| }); | ||
|
|
||
|
|
||
| // Diagnostic + Quick Fix | ||
| it('Should show diagnostic for invalid feature and apply quick fix', async function() { | ||
|
siddhusaladi marked this conversation as resolved.
Outdated
|
||
| this.timeout(120000); | ||
| logger.testStart('Testing diagnostic detection and quick fix for invalid feature'); | ||
|
|
||
|
|
||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
|
|
||
| logger.step(1, 'Finding feature line and replacing with invalid feature'); | ||
| const lineNumber = await editor.getLineOfText('jsp-2.3'); | ||
| logger.stepSuccess(1, `Found feature at line ${lineNumber}`); | ||
| const currentLine = await editor.getTextAtLine(lineNumber); | ||
| const modifiedLine = currentLine.replace('jsp-2.3', 'jsp-100.0'); | ||
| await editor.setTextAtLine(lineNumber, modifiedLine); | ||
| await editor.save(); | ||
| logger.stepSuccess(1, 'Changed to invalid feature jsp-100.0'); | ||
|
|
||
| logger.step(2, 'Waiting for diagnostic to appear'); | ||
| // LCLS produces: ERROR: The feature "jsp-100.0" does not exist. liberty-lemminx(incorrect_feature) | ||
| await wait.forCondition(async () => { | ||
| try { | ||
| const bottomBar = new BottomBarPanel(); | ||
| await bottomBar.toggle(true); | ||
| const problemsView = await bottomBar.openProblemsView(); | ||
| const markers = await problemsView.getAllVisibleMarkers(MarkerType.Error); | ||
| for (const marker of markers) { | ||
| const text = await marker.getText(); | ||
| if (text.includes('does not exist')) { | ||
| logger.stepSuccess(2, `Diagnostic found: ${text}`); | ||
| return true; | ||
| } | ||
| } | ||
| return undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }, { | ||
| timeout: 45000, | ||
| pollInterval: 2000, | ||
| message: 'Diagnostic did not appear for invalid feature' | ||
| }); | ||
|
|
||
|
|
||
| await new BottomBarPanel().toggle(false); | ||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
| await utils.waitForCondition(async () => { | ||
| const text = await editor.getText(); | ||
| return text.includes('<server') ? true : undefined; | ||
| }, 15); | ||
|
|
||
| logger.step(3, 'Selecting invalid feature text'); | ||
| await editor.selectText('jsp-100.0'); | ||
| logger.stepSuccess(3, 'Selected invalid feature'); | ||
|
|
||
| logger.step(4, 'Opening quick fix menu and applying fix'); | ||
| const modKey = process.platform === 'darwin' ? Key.COMMAND : Key.CONTROL; | ||
|
|
||
| // The quick-fix widget closes between polling iterations, so re-open it each time | ||
| const quickFixApplied = await wait.forCondition(async () => { | ||
| try { | ||
| await editor.selectText('jsp-100.0'); | ||
| await driver.actions().keyDown(modKey).sendKeys('.').keyUp(modKey).perform(); | ||
| await wait.sleep(1500); | ||
|
|
||
| const options = await driver.findElements( | ||
| By.css('.action-widget .action-list-item, .action-widget .monaco-list-row') | ||
| ); | ||
| for (const opt of options) { | ||
| const text = await opt.getText(); | ||
| logger.info(`Quick fix option: ${text}`); | ||
| // LCLS offers "Replace feature with jsp-2.2" and "Replace feature with jsp-2.3", accept either | ||
| if (text.includes('Replace feature with jsp-')) { | ||
| await driver.executeScript('arguments[0].click();', opt); | ||
| logger.stepSuccess(4, `Applied quick fix: ${text}`); | ||
| return true; | ||
| } | ||
| } | ||
| // Dismiss the menu so re-opening next iteration is clean | ||
| await driver.actions().sendKeys(Key.ESCAPE).perform(); | ||
| return undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }, { | ||
| timeout: 30000, | ||
| pollInterval: 3000, | ||
| message: 'Quick fix option did not appear' | ||
| }); | ||
|
|
||
| expect(quickFixApplied).to.be.true; | ||
|
|
||
| logger.step(5, 'Verifying fix was applied'); | ||
| const updatedContent = await utils.waitForCondition(async () => { | ||
| const text = await editor.getText(); | ||
| return !text.includes('jsp-100.0') ? text : undefined; | ||
| }, 15); | ||
| // Either jsp-2.2 or jsp-2.3 is a valid replacement — verify the bad value is gone | ||
| expect(updatedContent).to.match(/<feature>jsp-2\.\d<\/feature>/); | ||
| expect(updatedContent).to.not.include('jsp-100.0'); | ||
| logger.stepSuccess(5, 'Quick fix successfully replaced invalid feature'); | ||
|
|
||
| logger.testComplete('Diagnostic detected and quick fix applied successfully'); | ||
| }); | ||
|
|
||
|
|
||
| // Autocomplete for Features | ||
| it('Should provide autocomplete for Liberty features (#391)', async function() { | ||
|
siddhusaladi marked this conversation as resolved.
Outdated
|
||
| this.timeout(60000); | ||
| logger.testStart('Testing autocomplete for features'); | ||
|
|
||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
|
|
||
|
|
||
| // verify the list opens with items, then dismiss it and type the value directly | ||
| logger.step(1, 'Finding the closing </featureManager> line as insertion point'); | ||
| const featureManagerEndLine = await editor.getLineOfText('</featureManager>'); | ||
| logger.stepSuccess(1, `Found </featureManager> at line ${featureManagerEndLine}`); | ||
|
|
||
| logger.step(2, 'Typing empty feature tag on a new line'); | ||
| await editor.typeTextAt(featureManagerEndLine, 1, ' <feature></feature>'); | ||
| logger.stepSuccess(2, 'Typed empty feature tag'); | ||
|
|
||
| logger.step(3, 'Positioning cursor inside the empty feature tag'); | ||
| await editor.setCursor(featureManagerEndLine, 18); | ||
| logger.stepSuccess(3, 'Cursor positioned inside feature tag'); | ||
|
|
||
| logger.step(4, 'Opening content assist and verifying feature list appears'); | ||
| const assist = await editor.toggleContentAssist(true); | ||
|
|
||
| const items = await utils.waitForCondition(async () => { | ||
| try { | ||
| const all = await assist!.getItems(); | ||
| return all && all.length > 0 ? all : undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }, 10); | ||
| expect(items.length).to.be.greaterThan(0); | ||
| logger.stepSuccess(4, `Content assist opened with ${items.length} feature suggestions`); | ||
|
|
||
| // Dismiss the list and type the value directly | ||
| await editor.toggleContentAssist(false); | ||
| await driver.actions().sendKeys(Key.ESCAPE).perform(); | ||
|
|
||
| logger.step(5, 'Typing feature value directly after confirming list appeared'); | ||
| await editor.typeTextAt(featureManagerEndLine, 18, 'servlet-4.0'); | ||
| logger.stepSuccess(5, 'Typed feature value'); | ||
|
|
||
| logger.step(6, 'Verifying feature tag contains the typed value'); | ||
| const updatedContent = await utils.waitForCondition(async () => { | ||
| const text = await editor.getText(); | ||
| return text.includes('<feature>servlet-4.0</feature>') ? text : undefined; | ||
| }, 10); | ||
| expect(updatedContent).to.include('<feature>servlet-4.0</feature>'); | ||
| logger.stepSuccess(6, 'Feature tag correctly contains typed value'); | ||
|
|
||
| logger.testComplete('Autocomplete for features worked'); | ||
| }); | ||
|
|
||
|
|
||
| // Autocomplete for Config Stanzas | ||
| describe('Autocomplete for Configuration Stanzas (#392)', () => { | ||
|
siddhusaladi marked this conversation as resolved.
Outdated
|
||
|
|
||
| it('Should autocomplete <logging> stanza', async function() { | ||
| this.timeout(60000); | ||
| logger.testStart('Testing autocomplete for logging stanza'); | ||
|
|
||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
|
|
||
| logger.step(1, 'Finding </featureManager> as insertion point'); | ||
| const lineNumber = await editor.getLineOfText('</featureManager>'); | ||
| logger.stepSuccess(1, `Found featureManager end at line ${lineNumber}`); | ||
|
|
||
| logger.step(2, 'Typing partial stanza name on a new line'); | ||
| await editor.typeTextAt(lineNumber + 1, 1, ' <loggi'); | ||
| logger.stepSuccess(2, 'Typed partial stanza name'); | ||
|
|
||
| logger.step(3, 'Opening content assist for stanza completion'); | ||
| const assist = await utils.waitForCondition(async () => { | ||
| return await editor.toggleContentAssist(true) ?? undefined; | ||
| }, 10); | ||
| // Wait until the target item is present and interactable in the list | ||
| await utils.waitForCondition(async () => { | ||
| try { | ||
| const item = await assist.getItem('logging'); | ||
| return item ? true : undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }, 10); | ||
| logger.stepSuccess(3, 'Content assist opened'); | ||
|
|
||
| logger.step(4, 'Selecting "logging" from the completion list'); | ||
| await assist.select('logging'); | ||
| await editor.toggleContentAssist(false); | ||
| logger.stepSuccess(4, 'Selected logging from completion list'); | ||
|
|
||
| logger.step(5, 'Verifying logging stanza was inserted'); | ||
| // Poll until the editor reflects the completion rather than sleeping blindly | ||
| const updatedContent = await utils.waitForCondition(async () => { | ||
| const text = await editor.getText(); | ||
| return text.includes('<logging>') ? text : undefined; | ||
| }, 10); | ||
| expect(updatedContent).to.include('<logging>'); | ||
| expect(updatedContent).to.include('</logging>'); | ||
| logger.stepSuccess(5, 'Logging stanza autocomplete worked'); | ||
|
|
||
| logger.testComplete('Logging stanza autocomplete worked'); | ||
| }); | ||
|
|
||
| it('Should autocomplete <application> stanza', async function() { | ||
|
siddhusaladi marked this conversation as resolved.
Outdated
|
||
| this.timeout(60000); | ||
| logger.testStart('Testing autocomplete for application stanza'); | ||
|
|
||
| editor = await editorView.openEditor('server.xml') as TextEditor; | ||
|
|
||
| logger.step(1, 'Finding </featureManager> as insertion point'); | ||
| const lineNumber = await editor.getLineOfText('</featureManager>'); | ||
| logger.stepSuccess(1, `Found featureManager end at line ${lineNumber}`); | ||
|
|
||
| logger.step(2, 'Typing partial stanza name on a new line'); | ||
| await editor.typeTextAt(lineNumber + 1, 1, ' <applicati'); | ||
| logger.stepSuccess(2, 'Typed partial stanza name'); | ||
|
|
||
| logger.step(3, 'Opening content assist for stanza completion'); | ||
| const assist = await utils.waitForCondition(async () => { | ||
| return await editor.toggleContentAssist(true) ?? undefined; | ||
| }, 10); | ||
| // Wait until the target item is present and interactable in the list | ||
| await utils.waitForCondition(async () => { | ||
| try { | ||
| const item = await assist.getItem('application'); | ||
| return item ? true : undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| }, 10); | ||
| logger.stepSuccess(3, 'Content assist opened'); | ||
|
|
||
| logger.step(4, 'Selecting "application" from the completion list'); | ||
| await assist.select('application'); | ||
| await editor.toggleContentAssist(false); | ||
| logger.stepSuccess(4, 'Selected application from completion list'); | ||
|
|
||
| logger.step(5, 'Verifying application stanza was inserted'); | ||
| // Poll until the editor reflects the completion rather than sleeping blindly | ||
| const updatedContent = await utils.waitForCondition(async () => { | ||
| const text = await editor.getText(); | ||
| return /<application[>\s]/.test(text) ? text : undefined; | ||
| }, 10); | ||
| // Match <application> or <application > but not <webApplication> or <applicationManager> | ||
| expect(updatedContent).to.match(/<application[>\s]/); | ||
| expect(updatedContent).to.include('</application>'); | ||
| logger.stepSuccess(5, 'Application stanza autocomplete worked'); | ||
|
|
||
| logger.testComplete('Application stanza autocomplete worked'); | ||
| }); | ||
| }); | ||
| }); | ||
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.
Uh oh!
There was an error while loading. Please reload this page.