Skip to content

Commit a33f86e

Browse files
committed
Introduce Page Object Model for UI test suite
Add a page object layer under src/test/pages/ to encapsulate VS Code UI interactions, and refactor the LSP/snippet/diagnostic, hover, and dev-mode action tests to use it. Page objects expose intent-revealing methods and return data; assertions remain in the test files. Page objects added: - EditorPage: open/clear a file, move cursor to end, replace text on a line, hover, and expose the raw editor for generic operations (getEditor). - ProblemsPage: hasDiagnostic() to check the Problems view for a message. - CodeAssistPage: insertSnippet() wrapping the content-assist trigger/select/ close flow, including the null guard and settle delay. - QuickFixPage: applyFix() wrapping the quick-fix menu keystroke and option selection. - DashboardPage: runAction()/getSection()/getProjectItem() wrapping the Liberty Tools sidebar section+item fetch and launchDashboardAction, with fresh re-fetch each call to avoid stale-element errors and the mac/non-mac action pairing hidden internally. Tests refactored to use the page objects: - MavenTestLSPRestSnippetAndDiagnostic.ts - GradleTestLSPRestSnippetAndDiagnostic.ts - MavenTestLSPHover.ts / GradleTestLSPHover.ts - MavenTestDevModeActions.ts / GradleTestDevModeActions.ts UI mechanics (drivers, selectors, command IDs, section/item bookkeeping) move out of the test files into the page objects. Test behavior is unchanged; existing test cases, timeouts, and logging are preserved.
1 parent f6b0043 commit a33f86e

11 files changed

Lines changed: 494 additions & 579 deletions

src/test/GradleTestDevModeActions.ts

Lines changed: 79 additions & 117 deletions
Large diffs are not rendered by default.

src/test/GradleTestLSPHover.ts

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,24 @@
33
* Copyright IBM Corp. 2026
44
*/
55
import { expect } from 'chai';
6-
import { EditorView, TextEditor, VSBrowser, WebDriver, Workbench } from 'vscode-extension-tester';
6+
import { EditorView, TextEditor, VSBrowser, Workbench, WebDriver } from 'vscode-extension-tester';
77
import * as utils from './utils/testUtils';
88
import { logger } from './utils/testLogger';
99
import * as path from 'path';
10+
import { EditorPage } from './pages/EditorPage';
1011

1112
describe('LSP Hover tests for Gradle Project', () => {
12-
let editorView: EditorView;
13-
let editor: TextEditor;
14-
let wait: any;
15-
let driver: WebDriver;
16-
13+
let serverXml: EditorPage;
14+
1715
before(async function() {
1816
this.timeout(60000);
19-
logger.info('Setting up Gradle LSP Hover tests');
2017

21-
driver = VSBrowser.instance.driver;
22-
await VSBrowser.instance.openResources(utils.getGradleProjectPath());
18+
logger.info('Setting up Gradle LSP Hover tests');
2319

2420
// Wait for workbench to be ready
21+
await VSBrowser.instance.openResources(utils.getGradleProjectPath());
2522
await VSBrowser.instance.waitForWorkbench();
26-
editorView = new EditorView();
27-
wait = utils.getWaitHelper();
28-
23+
2924
// Open server.xml file once for all tests
3025
logger.info('Opening server.xml file for all tests');
3126
const serverXmlPath = path.resolve(
@@ -38,11 +33,7 @@ describe('LSP Hover tests for Gradle Project', () => {
3833
);
3934
logger.info(`Server.xml path: ${serverXmlPath}`);
4035

41-
await VSBrowser.instance.openResources(serverXmlPath, async () => {
42-
await wait.sleep(3000); // Allow time for file to load
43-
});
44-
45-
editor = await editorView.openEditor('server.xml') as TextEditor;
36+
serverXml = await new EditorPage().openFile(serverXmlPath, 'server.xml');
4637
logger.info('Server.xml file opened and editor obtained');
4738
});
4839

@@ -59,7 +50,7 @@ describe('LSP Hover tests for Gradle Project', () => {
5950
this.timeout(10000); // Increase timeout for cleanup operations
6051
// Close editor after all tests complete
6152
try {
62-
await editorView.closeAllEditors();
53+
await new EditorView().closeAllEditors();
6354
logger.info('Closed all editors after test suite');
6455
} catch (error) {
6556
logger.error('Failed to close editors in after hook', error);
@@ -101,18 +92,7 @@ describe('LSP Hover tests for Gradle Project', () => {
10192
logger.testStart(`Hover over ${testCase.element} shows Liberty Language Server documentation`);
10293

10394
try {
104-
logger.step(1, `Setting cursor position on ${testCase.element}`);
105-
await editor.setCursor(testCase.line, testCase.column);
106-
logger.stepSuccess(1, `Cursor positioned on ${testCase.element} at line ${testCase.line}`);
107-
108-
logger.step(2, 'Triggering hover via command palette');
109-
await new Workbench().executeCommand('editor.action.showHover');
110-
logger.stepSuccess(2, 'Hover command executed');
111-
112-
logger.step(3, 'Verifying hover widget appears with Liberty Language Server content');
113-
const driver = VSBrowser.instance.driver;
114-
const hoverText = await utils.waitForHoverWidget(driver, testCase.element, 15000);
115-
95+
const hoverText = await serverXml.hoverOver(testCase.line, testCase.column, testCase.element);
11696
expect(hoverText).to.not.be.empty;
11797
logger.stepSuccess(3, `Hover widget displayed with Liberty Language Server content for ${testCase.element}`);
11898

@@ -129,7 +109,7 @@ describe('LSP Hover tests for Gradle Project', () => {
129109
});
130110

131111
describe('LSP4Jakarta Hover tests in Java file', () => {
132-
let javaEditor: TextEditor;
112+
let javaFile: EditorPage;
133113

134114
before(async function() {
135115
this.timeout(60000);
@@ -148,12 +128,8 @@ describe('LSP Hover tests for Gradle Project', () => {
148128
'HelloServlet.java'
149129
);
150130
logger.info(`Java file path: ${javaFilePath}`);
151-
152-
await VSBrowser.instance.openResources(javaFilePath, async () => {
153-
await wait.sleep(3000); // Allow time for file to load
154-
});
131+
javaFile = await new EditorPage().openFile(javaFilePath, 'HelloServlet.java')
155132

156-
javaEditor = await editorView.openEditor('HelloServlet.java') as TextEditor;
157133
logger.info('HelloServlet.java file opened and editor obtained');
158134
});
159135

@@ -188,17 +164,7 @@ describe('LSP Hover tests for Gradle Project', () => {
188164
logger.testStart(`Hover over ${testCase.element} shows LSP4Jakarta documentation`);
189165

190166
try {
191-
logger.step(1, `Setting cursor position on ${testCase.element}`);
192-
await javaEditor.setCursor(testCase.line, testCase.column);
193-
logger.stepSuccess(1, `Cursor positioned on ${testCase.element} at line ${testCase.line}`);
194-
195-
logger.step(2, 'Triggering hover via command palette');
196-
await new Workbench().executeCommand('editor.action.showHover');
197-
logger.stepSuccess(2, 'Hover command executed');
198-
199-
logger.step(3, 'Verifying hover widget appears with LSP4Jakarta content');
200-
const driver = VSBrowser.instance.driver;
201-
const hoverText = await utils.waitForHoverWidget(driver, testCase.element, 15000);
167+
const hoverText = await javaFile.hoverOver(testCase.line, testCase.column, testCase.element);
202168

203169
expect(hoverText).to.not.be.empty;
204170
logger.stepSuccess(3, `Hover widget displayed with LSP4Jakarta content for ${testCase.element}`);

src/test/GradleTestLSPRestSnippetAndDiagnostic.ts

Lines changed: 66 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import { EditorView, TextEditor, VSBrowser, Workbench, WebDriver, BottomBarPanel
88
import * as utils from './utils/testUtils';
99
import { logger } from './utils/testLogger';
1010
import * as path from 'path';
11+
import { ProblemsPage } from './pages/ProblemsPage';
12+
import { EditorPage } from './pages/EditorPage';
13+
import { CodeAssistPage } from './pages/CodeAssistPage';
14+
import { QuickFixPage } from './pages/QuickFixPage';
1115

1216
describe('Rest Class Snippet Test for Gradle Project', () => {
13-
let editorView: EditorView;
14-
let javaEditor: TextEditor;
17+
let editorPage: EditorPage;
1518
let wait: any;
1619
let driver: WebDriver;
1720

@@ -26,18 +29,14 @@ describe('Rest Class Snippet Test for Gradle Project', () => {
2629
logger.info('Setting up rest_class snippet test');
2730

2831
driver = VSBrowser.instance.driver;
32+
wait = utils.getWaitHelper();
33+
// Open folder, wait for workbench
2934
await VSBrowser.instance.openResources(utils.getGradleProjectPath());
30-
31-
// Wait for workbench to be ready
3235
await VSBrowser.instance.waitForWorkbench();
33-
editorView = new EditorView();
34-
wait = utils.getWaitHelper();
3536

36-
// Open the file
37-
await VSBrowser.instance.openResources(testRestPath, async () => {
38-
await wait.sleep(3000);
39-
});
40-
javaEditor = await editorView.openEditor('TestRest.java') as TextEditor;
37+
// Open file and bind page object to editor
38+
editorPage = await new EditorPage().openFile(testRestPath, 'TestRest.java');
39+
4140
});
4241

4342
afterEach(async function() {
@@ -50,20 +49,28 @@ describe('Rest Class Snippet Test for Gradle Project', () => {
5049
});
5150

5251
after(async function() {
53-
this.timeout(10000); // Increase timeout for cleanup operations
54-
// Close editor after all tests complete
52+
this.timeout(15000);
5553
try {
56-
if(javaEditor){
57-
await javaEditor.setText('');
58-
await javaEditor.save();
59-
logger.info('Reset TestRest.java to empty after test')
60-
61-
}
62-
} catch (error){
63-
logger.error('Failed to reset TestRest.java ', error);
54+
if (editorPage) {
55+
// Select all text first, then clear
56+
const currentText = await editorPage.getEditor().getText();
57+
try {
58+
await editorPage.getEditor().selectText(currentText);
59+
await wait.sleep(300);
60+
} catch (selectError) {
61+
// selectText may fail but setText still works
62+
}
63+
64+
await editorPage.clear();
65+
logger.info('Reset TestRest.java to empty after test');
6466
}
67+
} catch (error) {
68+
logger.error('Failed to reset TestRest.java', error);
69+
}
70+
6571
try {
66-
await editorView.closeAllEditors();
72+
await new EditorView().closeAllEditors();
73+
await wait.sleep(500);
6774
logger.info('Closed all editors after test suite');
6875
} catch (error) {
6976
logger.error('Failed to close editors in after hook', error);
@@ -94,38 +101,32 @@ describe('Rest Class Snippet Test for Gradle Project', () => {
94101
logger.testStart('rest_class snippet inserts correct REST class');
95102

96103
// at the top of the rest_class snippet test, before positioning the cursor
97-
await javaEditor.setText('');
98-
await javaEditor.save();
104+
await editorPage.clear();
99105
await wait.sleep(1500); // let any auto-stub settle, then confirm
100-
const check = await javaEditor.getText();
106+
const check = await editorPage.getEditor().getText();
101107
logger.info('Buffer before snippet: ' + JSON.stringify(check));
102108

103109
try {
104110
logger.step(1, 'Positioning cursor for snippet insertion');
105111
// Position cursor at end of file
106-
const lastLine = (await javaEditor.getText()).split('\n').length;
107-
await javaEditor.setCursor(lastLine - 1, 1);
112+
await editorPage.moveCursorToEnd();
108113

109-
logger.step(2, 'Typing "rest" to trigger snippet');
110-
await javaEditor.typeText('rest');
111-
logger.stepSuccess(2, 'Typed "rest"');
114+
// type rest and insert snippet
115+
logger.step(2, 'Opening content assist');
116+
const codeAssist = new CodeAssistPage();
117+
await codeAssist.insertSnippet(editorPage, 'rest', 'rest_class');
118+
logger.stepSuccess(2, 'Snippet inserted');
119+
await wait.sleep(1000); // Give time for snippet to fully expand
112120

113-
logger.step(3, 'Opening content assist');
114-
const assist = await javaEditor.toggleContentAssist(true);
115-
if (assist) {
116-
logger.step(4,'Selecting rest_class snippet');
117-
await assist.select('rest_class');
118-
await new Promise(res => setTimeout(res, 600)); // 300–800ms
119-
logger.stepSuccess(4, 'rest_class snippet selected');
120-
}
121-
await javaEditor.toggleContentAssist(false);
122-
123-
logger.step(5, 'Verifying snippet insertion');
124-
const codeInsertion = await javaEditor.getText();
125-
logger.info('Inserted code snapshot: ' + codeInsertion);
121+
logger.step(3, 'Verifying snippet insertion');
122+
const codeInsertion = await editorPage.getEditor().getText();
126123
expect(codeInsertion).to.include('@GET')
127124
expect(codeInsertion).to.include('methodname');
128-
logger.stepSuccess(5, 'Snippet rest_class was inserted correctly');
125+
logger.stepSuccess(3, 'Snippet rest_class was inserted correctly');
126+
127+
// Save the file so the content persists for the next test
128+
await editorPage.getEditor().save();
129+
await wait.sleep(500);
129130

130131
logger.testComplete('rest_class snippet inserts correct REST class');
131132
} catch (error) {
@@ -137,96 +138,37 @@ describe('Rest Class Snippet Test for Gradle Project', () => {
137138
this.timeout(90000);
138139
logger.testStart('Diagnostic for a private @GET method and quick fix clears it');
139140
try {
140-
// Find the method with @GET and change it to private
141-
let lineNum = await javaEditor.getLineOfText('methodname');
142-
if (lineNum < 1) throw new Error('Could not find the methodname line');
143-
const oldLine = await javaEditor.getTextAtLine(lineNum);
144-
const newLine = oldLine.replace("public", "private");
145-
await javaEditor.setTextAtLine(lineNum, newLine);
146-
147-
// Save file and wait for reanalysis
148-
await javaEditor.save();
149-
await wait.sleep(500);
150-
151-
// Open problems view
152-
const bottomBar = new BottomBarPanel();
153-
await bottomBar.toggle(true);
154-
let problemsView = await bottomBar.openProblemsView();
155-
let markers = await problemsView.getAllVisibleMarkers(MarkerType.Any);
156-
157-
// Check if the marker is present
158-
let found = false;
159-
for (const marker of markers) {
160-
const text = await marker.getText();
161-
// Check if text contains your diagnostic message
162-
if(text.includes('Only public methods can be exposed as resource methods.')){
163-
found = true;
164-
break;
165-
}
166-
}
167-
expect(found).to.be.true;
141+
// Find the method with @GET and change it to private
142+
await editorPage.replaceTextWithinLineContaining('methodname', 'public' , 'private');
143+
144+
// Save file and wait for reanalysis
145+
await editorPage.getEditor().save();
146+
await wait.sleep(500);
147+
148+
// Check that the diagnostic is there
149+
let problems = new ProblemsPage();
150+
const found = await problems.hasDiagnostic('Only public methods can be exposed as resource methods.');
151+
expect(found).to.be.true;
168152
logger.testComplete('Diagnostic shows for private @GET method');
169153

170154
// Quick fix implementation to get rid of diagnostic
171155
// Re-find line and place cursor on "methodname()"
172-
const buffer = await javaEditor.getText();
156+
const buffer = await editorPage.getEditor().getText();
173157
logger.info('Buffer before quick fix: ' + JSON.stringify(buffer));
174158

175-
// Get column on the method name
176-
await javaEditor.selectText('methodname');
177-
await wait.sleep(300);
178-
179-
// Open the quick-fix menu with Cmd+. (Mac) / Ctrl+. (Linux/Windows)
180-
const modKey = process.platform === 'darwin' ? Key.COMMAND : Key.CONTROL;
181-
await driver.actions().keyDown(modKey).sendKeys('.').keyUp(modKey).perform();
182-
await wait.sleep(2000);
183-
184-
// Click on the option to make the method public within the quick-fixes options
185-
const options = await driver.findElements(By.css('.action-widget .action-list-item, .action-widget .monaco-list-row'));
186-
let clicked = false;
187-
for (const opt of options) {
188-
const text = await opt.getText();
189-
logger.info('OPTION: ' + JSON.stringify(text));
190-
if (text.toLowerCase().includes('make method public')) {
191-
await driver.executeScript('arguments[0].click();', opt);
192-
clicked = true;
193-
break;
194-
}
195-
}
196-
if (!clicked) {
197-
await driver.actions().sendKeys(Key.ESCAPE).perform(); // close menu cleanly
198-
throw new Error('No "make public" quick fix was offered — see OPTION logs above');
199-
}
159+
await new QuickFixPage().applyFix(editorPage, 'methodname', 'make method public');
160+
await wait.sleep(3000);
161+
await editorPage.getEditor().save();
162+
await wait.sleep(5000);
200163

201-
await wait.sleep(3000);
202-
await javaEditor.save();
203-
await wait.sleep(5000);
204-
205-
// Check quick fix was implemented at correct line number
206-
const after = await javaEditor.getText();
207-
logger.info('Buffer after quick fix: ' + JSON.stringify(after));
208-
expect(after).to.include('public String methodname');
209-
210-
problemsView = await bottomBar.openProblemsView();
211-
markers = await problemsView.getAllVisibleMarkers(MarkerType.Any);
212-
213-
// Check if the marker is present
214-
let stillPresent = false;
215-
for (const marker of markers) {
216-
const text = await marker.getText();
217-
// Check if text contains diagnostic message
218-
if(text.includes('Only public methods can be exposed as resource methods.')){
219-
stillPresent = true;
220-
break;
221-
}
222-
}
223-
expect(stillPresent).to.be.false;
224-
logger.testComplete('Diagnostic no longer shows for public @GET method as expected');
164+
const after = await editorPage.getEditor().getText();
165+
expect(after).to.include('public String methodname');
166+
expect(await problems.hasDiagnostic('Only public methods can be exposed as resource methods.')).to.be.false;
225167

226168
} catch (error) {
227169
logger.testFailed('Diagnostic/quick-fix test flow failed', error);
228-
throw error;
229-
}
170+
throw error;
171+
}
230172

231173
});
232174
});

0 commit comments

Comments
 (0)