Skip to content

Commit a8d1e87

Browse files
committed
test: upgrade e2e test framework
1 parent c677dbc commit a8d1e87

File tree

13 files changed

+901
-63
lines changed

13 files changed

+901
-63
lines changed

.github/workflows/linuxUI.yml

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,13 @@ jobs:
4444
- name: Build VSIX file
4545
run: vsce package
4646

47-
- name: UI Test
48-
continue-on-error: true
49-
id: test
50-
run: DISPLAY=:99 npm run test-ui
47+
- name: E2E Test (Playwright)
48+
run: DISPLAY=:99 npm run test-e2e
5149

52-
- name: Retry UI Test 1
53-
continue-on-error: true
54-
if: steps.test.outcome=='failure'
55-
id: retry1
56-
run: |
57-
git reset --hard
58-
git clean -fd
59-
DISPLAY=:99 npm run test-ui
60-
61-
- name: Retry UI Test 2
62-
continue-on-error: true
63-
if: steps.retry1.outcome=='failure'
64-
id: retry2
65-
run: |
66-
git reset --hard
67-
git clean -fd
68-
DISPLAY=:99 npm run test-ui
69-
70-
- name: Set test status
71-
if: ${{ steps.test.outcome=='failure' && steps.retry1.outcome=='failure' && steps.retry2.outcome=='failure' }}
72-
run: |
73-
echo "Tests failed"
74-
exit 1
75-
76-
- name: Print language server Log
77-
if: ${{ failure() }}
78-
run: find ./test-resources/settings/User/workspaceStorage/*/redhat.java/jdt_ws/.metadata/.log -print -exec cat '{}' \;;
50+
- name: Upload test results
51+
if: ${{ always() }}
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: e2e-results-linux
55+
path: test-results/
56+
retention-days: 7

.github/workflows/windowsUI.yml

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,13 @@ jobs:
4444
- name: Build VSIX file
4545
run: vsce package
4646

47-
- name: UI Test
48-
continue-on-error: true
49-
id: test
50-
run: npm run test-ui
47+
- name: E2E Test (Playwright)
48+
run: npm run test-e2e
5149

52-
- name: Retry UI Test 1
53-
continue-on-error: true
54-
if: steps.test.outcome=='failure'
55-
id: retry1
56-
run: |
57-
git reset --hard
58-
git clean -fd
59-
npm run test-ui
60-
61-
- name: Retry UI Test 2
62-
continue-on-error: true
63-
if: steps.retry1.outcome=='failure'
64-
id: retry2
65-
run: |
66-
git reset --hard
67-
git clean -fd
68-
npm run test-ui
69-
70-
- name: Set test status
71-
if: ${{ steps.test.outcome=='failure' && steps.retry1.outcome=='failure' && steps.retry2.outcome=='failure' }}
72-
run: |
73-
echo "Tests failed"
74-
exit 1
75-
76-
- name: Print language server Log if job failed
77-
if: ${{ failure() }}
78-
run: Get-ChildItem -Path ./test-resources/settings/User/workspaceStorage/*/redhat.java/jdt_ws/.metadata/.log | cat
50+
- name: Upload test results
51+
if: ${{ always() }}
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: e2e-results-windows
55+
path: test-results/
56+
retention-days: 7

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,7 @@
12631263
"watch": "webpack --mode development --watch",
12641264
"test": "tsc -p . && webpack --config webpack.config.js --mode development && node ./dist/test/index.js",
12651265
"test-ui": "tsc -p . && webpack --config webpack.config.js --mode development && node ./dist/test/ui/index.js",
1266+
"test-e2e": "npx playwright test --config test/e2e/playwright.config.ts",
12661267
"build-server": "node scripts/buildJdtlsExt.js",
12671268
"vscode:prepublish": "tsc -p ./ && webpack --mode production",
12681269
"tslint": "tslint -t verbose --project tsconfig.json"
@@ -1284,6 +1285,7 @@
12841285
"tslint": "^6.1.3",
12851286
"typescript": "^4.9.4",
12861287
"vscode-extension-tester": "^8.23.0",
1288+
"@playwright/test": "^1.50.0",
12871289
"webpack": "^5.105.0",
12881290
"webpack-cli": "^4.10.0"
12891291
},

test/e2e/fixtures/baseTest.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
/**
5+
* Playwright test fixture that launches VS Code via Electron,
6+
* opens a temporary copy of a test project, and tears everything
7+
* down after the test.
8+
*
9+
* Usage in test files:
10+
*
11+
* import { test, expect } from "../fixtures/baseTest";
12+
*
13+
* test("my test", async ({ page }) => {
14+
* // `page` is a Playwright Page attached to VS Code
15+
* });
16+
*/
17+
18+
import { _electron, test as base, type Page } from "@playwright/test";
19+
import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from "@vscode/test-electron";
20+
import * as fs from "fs-extra";
21+
import * as os from "os";
22+
import * as path from "path";
23+
24+
export { expect } from "@playwright/test";
25+
26+
// Root of the extension source tree
27+
const EXTENSION_ROOT = path.join(__dirname, "..", "..", "..");
28+
// Root of the test data projects
29+
const TEST_DATA_ROOT = path.join(EXTENSION_ROOT, "test");
30+
31+
export type TestOptions = {
32+
/** VS Code version to download, default "stable" */
33+
vscodeVersion: string;
34+
/** Relative path under `test/` to the project to open (e.g. "maven") */
35+
testProjectDir: string;
36+
};
37+
38+
type TestFixtures = TestOptions & {
39+
/** Playwright Page connected to the VS Code Electron window */
40+
page: Page;
41+
};
42+
43+
export const test = base.extend<TestFixtures>({
44+
vscodeVersion: [process.env.VSCODE_VERSION || "stable", { option: true }],
45+
testProjectDir: ["maven", { option: true }],
46+
47+
page: async ({ vscodeVersion, testProjectDir }, use, testInfo) => {
48+
// 1. Create a temp directory and copy the test project into it.
49+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "java-dep-e2e-"));
50+
const projectName = path.basename(testProjectDir);
51+
const projectDir = path.join(tmpDir, projectName);
52+
fs.copySync(path.join(TEST_DATA_ROOT, testProjectDir), projectDir);
53+
54+
// 2. Resolve VS Code executable.
55+
const vscodePath = await downloadAndUnzipVSCode(vscodeVersion);
56+
const [, ...cliArgs] = resolveCliArgsFromVSCodeExecutablePath(vscodePath);
57+
58+
// 3. Launch VS Code as an Electron app.
59+
const electronApp = await _electron.launch({
60+
executablePath: vscodePath,
61+
env: { ...process.env, NODE_ENV: "development" },
62+
args: [
63+
"--no-sandbox",
64+
"--disable-gpu-sandbox",
65+
"--disable-updates",
66+
"--skip-welcome",
67+
"--skip-release-notes",
68+
"--disable-workspace-trust",
69+
"--password-store=basic",
70+
...cliArgs,
71+
`--extensionDevelopmentPath=${EXTENSION_ROOT}`,
72+
projectDir,
73+
],
74+
});
75+
76+
const page = await electronApp.firstWindow();
77+
78+
// 4. Optional tracing
79+
if (testInfo.retry > 0 || !process.env.CI) {
80+
await page.context().tracing.start({ screenshots: true, snapshots: true, title: testInfo.title });
81+
}
82+
83+
// ---- hand off to the test ----
84+
await use(page);
85+
86+
// ---- teardown ----
87+
// Save trace on failure/retry
88+
if (testInfo.status !== "passed" || testInfo.retry > 0) {
89+
const tracePath = testInfo.outputPath("trace.zip");
90+
try {
91+
await page.context().tracing.stop({ path: tracePath });
92+
testInfo.attachments.push({ name: "trace", path: tracePath, contentType: "application/zip" });
93+
} catch {
94+
// Tracing may not have been started
95+
}
96+
}
97+
98+
await electronApp.close();
99+
100+
// Clean up temp directory
101+
try {
102+
fs.rmSync(tmpDir, { force: true, recursive: true });
103+
} catch (e) {
104+
console.warn(`Warning: failed to clean up ${tmpDir}: ${e}`);
105+
}
106+
},
107+
});

test/e2e/globalSetup.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from "@vscode/test-electron";
5+
import * as childProcess from "child_process";
6+
import * as path from "path";
7+
8+
/**
9+
* Global setup runs once before all test files.
10+
* It downloads VS Code, then installs the redhat.java extension and our own
11+
* VSIX so that every test run starts from an identical, pre-provisioned state.
12+
*/
13+
export default async function globalSetup(): Promise<void> {
14+
// Download VS Code stable (or the version configured via VSCODE_VERSION env).
15+
const vscodeVersion = process.env.VSCODE_VERSION || "stable";
16+
console.log(`[globalSetup] Downloading VS Code ${vscodeVersion}…`);
17+
const vscodePath = await downloadAndUnzipVSCode(vscodeVersion);
18+
const [cli, ...cliArgs] = resolveCliArgsFromVSCodeExecutablePath(vscodePath);
19+
20+
// Install the Language Support for Java extension from the Marketplace.
21+
console.log("[globalSetup] Installing redhat.java extension…");
22+
childProcess.execFileSync(cli, [...cliArgs, "--install-extension", "redhat.java"], {
23+
encoding: "utf-8",
24+
stdio: "inherit",
25+
timeout: 120_000,
26+
});
27+
28+
// Install our own VSIX if one exists (built by `vsce package`).
29+
const vsixGlob = path.join(__dirname, "..", "..", "*.vsix");
30+
const glob = require("glob");
31+
const vsixFiles: string[] = glob.sync(vsixGlob);
32+
if (vsixFiles.length > 0) {
33+
const vsix = vsixFiles[0];
34+
console.log(`[globalSetup] Installing VSIX ${path.basename(vsix)}…`);
35+
childProcess.execFileSync(cli, [...cliArgs, "--install-extension", vsix], {
36+
encoding: "utf-8",
37+
stdio: "inherit",
38+
timeout: 60_000,
39+
});
40+
} else {
41+
console.log("[globalSetup] No VSIX found — extension will be loaded via extensionDevelopmentPath");
42+
}
43+
}

test/e2e/playwright.config.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import { defineConfig } from "@playwright/test";
5+
import * as path from "path";
6+
7+
export default defineConfig({
8+
testDir: path.join(__dirname, "tests"),
9+
reporter: process.env.CI
10+
? [["list"], ["junit", { outputFile: path.join(__dirname, "..", "..", "test-results", "e2e-results.xml") }]]
11+
: "list",
12+
// Java Language Server can take 2-3 minutes to fully index on first run.
13+
timeout: 180_000,
14+
// Run tests sequentially — launching multiple VS Code instances is too resource-heavy.
15+
workers: 1,
16+
// Allow one retry in CI to handle transient environment issues.
17+
retries: process.env.CI ? 1 : 0,
18+
expect: {
19+
timeout: 30_000,
20+
},
21+
globalSetup: path.join(__dirname, "globalSetup.ts"),
22+
use: {
23+
trace: "on-first-retry",
24+
},
25+
outputDir: path.join(__dirname, "..", "..", "test-results", "e2e"),
26+
});

0 commit comments

Comments
 (0)