From 64dbb6df3492e352b72c9d4008e44c5a62648b96 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 8 Jun 2026 16:03:22 +0800 Subject: [PATCH] =?UTF-8?q?test(e2e):=20add=20Phase=20C=20plans=20?= =?UTF-8?q?=E2=80=94=20project=20create,=20export=20jar,=20delete=20perman?= =?UTF-8?q?ently?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds three new E2E test plans that cover the remaining high-value Java Manager commands not yet exercised by Phase A or Phase B: - test/e2e-plans/java-dep-project-creation.yaml Covers java.project.create — the flagship "Java: Create Java Project..." wizard, exercised on the "No build tools" branch (the only project type whose scaffolding lives in this repo; every other type delegates to a third-party extension). Asserts on-disk presence of the scaffolded template files. - test/e2e-plans/java-dep-export-jar.yaml Covers java.view.package.exportJar — the multi-step Export Jar wizard. Pins java.project.exportJar.targetPath via user settings so the showSaveDialog branch is skipped, then asserts the jar file exists at the configured target path after the Jdtls generation step finishes. - test/e2e-plans/java-dep-delete-permanent.yaml Covers java.view.package.deleteFilePermanently — the non-trash branch of file removal. Has no plain-click UI affordance for regular local files (the context menu and delete keybinding are both gated on !explorerResourceMoveableToTrash), so the command is invoked directly by id with tree-selection fallback, same pattern as removeLibrary in the classpath plan. All three plans pass locally (45/45 new steps). Full suite remains green across 11 plans / 308 steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/e2e-plans/java-dep-delete-permanent.yaml | 144 +++++++++++++++ test/e2e-plans/java-dep-export-jar.yaml | 162 +++++++++++++++++ test/e2e-plans/java-dep-project-creation.yaml | 166 ++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 test/e2e-plans/java-dep-delete-permanent.yaml create mode 100644 test/e2e-plans/java-dep-export-jar.yaml create mode 100644 test/e2e-plans/java-dep-project-creation.yaml diff --git a/test/e2e-plans/java-dep-delete-permanent.yaml b/test/e2e-plans/java-dep-delete-permanent.yaml new file mode 100644 index 00000000..8089ede7 --- /dev/null +++ b/test/e2e-plans/java-dep-delete-permanent.yaml @@ -0,0 +1,144 @@ +# Test Plan: Java Dependency — Permanent Delete +# +# Covers java.view.package.deleteFilePermanently (the "shift+delete" / +# non-trash branch of file removal). The companion command +# java.view.package.moveFileToTrash is already covered by +# java-dep-file-operations.yaml. +# +# Why this is a separate plan +# ─────────────────────────── +# The permanent-delete command has no plain-click UI affordance on regular +# local files: the JAVA PROJECTS context-menu entry (package.json:812-820) +# and the `delete` keybinding (package.json:485-490) are both gated on +# `!explorerResourceMoveableToTrash`, so files in `test/maven` only ever +# see "Delete" (move-to-trash). We therefore invoke the command directly +# via id and rely on tree selection to supply the target node — the same +# pattern the classpath plan uses for `removeLibrary`. +# +# The confirmation dialog also differs from moveFileToTrash: the prompt +# says "permanently delete" instead of "delete", and the action button is +# labelled "Delete" instead of "Move to Recycle Bin" (see +# src/explorerCommands/delete.ts line 17 + getInformationMessage). +# +# Usage: +# npx autotest run test/e2e-plans/java-dep-delete-permanent.yaml --vsix + +name: "Java Dependency — Permanent Delete" +description: | + Tests the java.view.package.deleteFilePermanently command on a regular + Maven-project file. Invokes the command by id (no UI affordance on local + files), confirms the "permanently delete" dialog, and verifies the file + is gone from disk and from the Java Projects tree. + +setup: + extension: "redhat.java" + vscodeVersion: "stable" + workspace: "../maven" + timeout: 180 + settings: + java.configuration.checkProjectSettingsExclusions: false + workbench.startupEditor: "none" + +steps: + # ── Setup: wait for LS, free sidebar space, focus Java Projects ── + - id: "ls-ready" + action: "waitForLanguageServer" + # No `verify:` — `waitForLanguageServer` is itself the deterministic + # readiness check. The AFTER screenshot may transiently show + # "Java: Building - 0%" which a strict LLM mis-reads as a failure. + timeout: 180 + + - id: "close-aux-bar" + action: "executeVSCodeCommand workbench.action.closeAuxiliaryBar" + verify: "Auxiliary bar (Chat) closed" + + - id: "collapse-outline" + action: "collapseSidebarSection OUTLINE" + + - id: "collapse-timeline" + action: "collapseSidebarSection TIMELINE" + + - id: "collapse-workspace-root" + action: "collapseWorkspaceRoot" + + - id: "focus-java-projects" + action: "executeVSCodeCommand javaProjectExplorer.focus" + verify: "Java Projects view is focused" + + - id: "wait-tree-load" + action: "wait 3 seconds" + + # ── Reveal App1.java via link-with-editor, then select it ── + # Opening the file makes link-with-editor expand the tree path and + # reveal+select App1 deterministically — much more reliable than + # manual expandTreeItem chains on the virtualised tree. + - id: "open-target-file" + action: "open file App1.java" + waitBefore: 2 + + - id: "collapse-workspace-root-2" + action: "collapseWorkspaceRoot" + + - id: "focus-java-projects-2" + action: "executeVSCodeCommand javaProjectExplorer.focus" + waitBefore: 2 + + # Click the tree item to guarantee it's the active selection — getCmdNode + # (explorerCommands/utility.ts:38) falls back to `selectedNodes[0]` when + # the command is invoked without a node argument. Linking-with-editor + # already auto-selected App1 when the file was opened, but clicking + # re-asserts the selection deterministically after the focus-java-projects + # round trip. + # + # We intentionally do NOT verify the App1 tree row here. On 1024x768 CI + # displays the row gets virtualised out of view in the brief window + # between `open file` and the click, and an inView:"Java Projects" + # verifyTreeItem then times out — even though the underlying selection + # is set correctly (proven by the subsequent delete succeeding). The + # post-delete `verify-file-gone` + `verify-tree-item-gone` block is the + # authoritative ground truth for whether the right node was targeted. + - id: "select-app1" + action: "click App1 tree item" + waitBefore: 1 + + # ── Invoke java.view.package.deleteFilePermanently ── + # The handler at views/dependencyExplorer.ts:177 takes `node?: DataNode`. + # With no node arg, getCmdNode uses the current selection (set by the + # `click App1 tree item` step above). DataNode is a class instance with + # methods (.getChildren()) so passing a POJO would crash — selection + # fallback is the only viable path from a smoke-test. + - id: "invoke-delete-permanently" + action: "executeVSCodeCommand java.view.package.deleteFilePermanently" + + # `expectConfirmDialog` waits for the dialog and clicks the first + # recognized confirm button (autotest knows "Delete" is one of them, see + # dialogOperations.ts:16). It's the strict variant — throws if no dialog + # appears, surfacing a silently-failed command invocation immediately + # instead of 15s later when verifyFile times out. + - id: "confirm-delete" + action: "expectConfirmDialog" + + - id: "wait-after-delete" + action: "wait 5 seconds" + + # ── Verify deletion on disk AND in the tree ── + # The disk check is the strongest signal: useTrash=false routes through + # workspace.fs.delete with the OS-level unlink, so a passing verifyFile + # exists:false proves the permanent-delete path actually fired (rather + # than silently downgrading to a no-op or moving to trash). + - id: "verify-file-gone" + action: "wait 1 seconds" + verifyFile: + path: "${workspaceFolder}/src/main/java/com/mycompany/app1/App1.java" + exists: false + timeout: 15 + + - id: "verify-tree-item-gone" + action: "wait 1 seconds" + # No `verify:` — verifyTreeItem is authoritative. + verifyTreeItem: + name: "App1" + exact: true + visible: false + inView: "Java Projects" + timeout: 15 diff --git a/test/e2e-plans/java-dep-export-jar.yaml b/test/e2e-plans/java-dep-export-jar.yaml new file mode 100644 index 00000000..3abecd71 --- /dev/null +++ b/test/e2e-plans/java-dep-export-jar.yaml @@ -0,0 +1,162 @@ +# Test Plan: Java Dependency — Export Jar +# +# Covers java.view.package.exportJar — the multi-step wizard that builds a +# runnable jar from a Java project. The command is contributed both as the +# title-bar `$(export)` icon on the JAVA PROJECTS workspace-root node +# (package.json:858-861, group=inline) and as a context-menu entry, but we +# invoke it directly by id: the inline icon is rendered against the +# `java:workspace` viewItem (not the Maven project node `my-app`), which +# is fragile to locate by name, and ResolveJavaProject auto-resolves when +# there's exactly one project in the workspace — so direct invocation +# bypasses one wizard step that's not the command's responsibility. +# +# Wizard step machine (BuildArtifactTaskProvider.ts:284 `createJarFile`): +# 1. ResolveJavaProject → auto when single-project; quick-pick otherwise +# 2. ResolveMainClass → quick-pick of main classes + "" +# 3. GenerateJar +# a. generateClasspaths → multi-select quick-pick if >1 dependency item +# b. showSaveDialog → only if outputPath === "" (skipped when +# java.project.exportJar.targetPath is set +# to a non-empty value; we set it via +# workspaceSettings to keep the output +# filename deterministic) +# c. Jdtls.exportJar → writes the jar file +# +# Verification strategy +# ───────────────────── +# The jar is written to a known absolute path inside the workspace +# (`output.jar`). We assert with `verifyFile exists: true` — the strongest +# possible signal that the full wizard completed end-to-end, not just that +# the command was dispatched. The Jdtls export runs in a hidden Pseudoterminal +# (BuildArtifactTaskProvider line 91: `presentationOptions.reveal = Never`), +# so there is no terminal text to inspect; the file on disk is the only +# unambiguous post-condition. +# +# Usage: +# npx autotest run test/e2e-plans/java-dep-export-jar.yaml --vsix + +name: "Java Dependency — Export Jar" +description: | + Exercises the multi-step Export Jar wizard end-to-end on the maven + fixture: triggers the command, picks the main class, accepts the default + classpath element selection, and verifies that the resulting jar file + exists at the configured target path. + +setup: + extension: "redhat.java" + vscodeVersion: "stable" + workspace: "../maven" + # Bumped above the standard 180s — the wizard runs a full workspace build + # (await buildWorkspace() in executeExportJarTask) before the first + # quick-pick appears, which on a cold JDT-LS warmup commonly takes 60-90s. + timeout: 360 + settings: + java.configuration.checkProjectSettingsExclusions: false + workbench.startupEditor: "none" + # Pinning java.project.exportJar.targetPath to an absolute deterministic + # path bypasses the showSaveDialog branch in GenerateJarExecutor + # (BuildArtifactTaskProvider lines 240-244 short-circuit only when + # outputPath === ""), and lets the verifyFile assertion target a stable + # location regardless of how the fixture's worktree is named. + # + # We use user-level `settings:` (not `workspaceSettings:`) because + # autotest's workspaceSettings merge uses JSON.parse on the existing + # `.vscode/settings.json`, and the maven fixture's settings.json + # contains JSONC `//` comments which fail JSON.parse. User settings + # are written fresh each run (user-data-dir is wiped on launch) so + # there is no JSONC merge hazard. The Settings.getExportJarTargetPath + # config read is unscoped, so user-level setting takes effect identically. + java.project.exportJar.targetPath: "${workspaceFolder}/output.jar" + +steps: + # ── Setup: wait for LS, free sidebar space, focus Java Projects ── + - id: "ls-ready" + action: "waitForLanguageServer" + # No `verify:` — `waitForLanguageServer` is itself the deterministic + # readiness check. The AFTER screenshot may transiently show + # "Java: Building - 0%" which a strict LLM mis-reads as a failure. + timeout: 180 + + - id: "close-aux-bar" + action: "executeVSCodeCommand workbench.action.closeAuxiliaryBar" + verify: "Auxiliary bar (Chat) closed" + + - id: "collapse-outline" + action: "collapseSidebarSection OUTLINE" + + - id: "collapse-timeline" + action: "collapseSidebarSection TIMELINE" + + - id: "collapse-workspace-root" + action: "collapseWorkspaceRoot" + + - id: "focus-java-projects" + action: "executeVSCodeCommand javaProjectExplorer.focus" + verify: "Java Projects view is focused" + + - id: "wait-tree-load" + action: "wait 3 seconds" + + # ── Trigger the Export Jar wizard ── + # Direct command invocation avoids the brittle inline-icon-on-workspace- + # node click path (the `$(export)` icon is contributed against viewItem + # `java:workspace`, not against the Maven project node `my-app`, and + # locating workspace-level inline icons in autotest is fragile). + # ResolveJavaProject auto-resolves for single-project workspaces. + - id: "invoke-export-jar" + action: "executeVSCodeCommand java.view.package.exportJar" + + # The wizard first triggers a full workspace build (await buildWorkspace() + # in executeExportJarTask) before any UI appears. On a cold JDT-LS this + # typically takes 30-60s; the Jdtls.getMainClasses fetch then needs + # another 2-5s before the first quick-pick is shown. Wait generously. + - id: "wait-build-complete" + action: "wait 60 seconds" + + # ── Step 2: pick the main class ── + # The maven fixture has a single class with a `public static void main` + # entry point: com.mycompany.app.App. App1 has no main, so the only + # quick-pick options are "App" and "". We pick "App" + # explicitly so the resulting jar is runnable. + - id: "pick-main-class" + action: "select App option" + # No `verify:` — selectPaletteOption is the action and verification in + # one. The quick-pick closes on selection; the AFTER screenshot may + # already show the next quick-pick (classpath elements), which a + # strict LLM could mis-read as "App selection was lost". + + - id: "wait-after-main-class" + action: "wait 5 seconds" + + # ── Step 3a: accept the pre-selected classpath elements ── + # GenerateJarExecutor.generateClasspaths builds a list of runtime / test + # output folders + dependency artifacts, pre-selects everything tagged + # `runtime`, and shows a multi-select QuickPick. For the maven fixture + # this includes target/classes plus any resolved JUnit jars under test + # scope. The pre-selection is the sane default — accepting it gives a + # runnable jar without needing to compute which items to check. + # + # `confirmQuickInput` presses Enter on the open quick-pick widget without + # typing anything, which leaves selections untouched and submits. + - id: "accept-classpath-elements" + action: "confirmQuickInput" + + # ── Step 3c: wait for Jdtls to generate the jar ── + # Jdtls.exportJar writes the jar via a JDT LSP request. The custom + # pseudoterminal stays hidden (presentationOptions.reveal=Never), so + # there is no terminal-text signal — we just wait long enough for the + # filesystem write to settle. + - id: "wait-jar-generated" + action: "wait 30 seconds" + + # ── Verification: jar file exists at the configured target path ── + # This is the strongest possible end-to-end check: the file appearing + # on disk proves ResolveJavaProject + ResolveMainClass + generateClasspaths + # + Jdtls.exportJar all completed in order. A failure here pinpoints + # the wizard breaking; a pass means the full happy path worked. + - id: "verify-jar-created" + action: "wait 1 seconds" + verifyFile: + path: "${workspaceFolder}/output.jar" + exists: true + timeout: 60 diff --git a/test/e2e-plans/java-dep-project-creation.yaml b/test/e2e-plans/java-dep-project-creation.yaml new file mode 100644 index 00000000..e705673c --- /dev/null +++ b/test/e2e-plans/java-dep-project-creation.yaml @@ -0,0 +1,166 @@ +# Test Plan: Java Dependency — Create Project +# +# Covers java.project.create — the flagship "Java: Create Java Project..." +# wizard. We exercise the "No build tools" branch (a.k.a. invisible / +# unmanaged-folder project) because it is the only project type whose +# scaffolding lives entirely inside this extension (`templates/invisible- +# project/`) — every other type (Maven, Gradle, Spring Boot, Quarkus, ...) +# delegates to a third-party extension's create-command, which is out of +# scope for this repo's E2E coverage. +# +# Wizard flow (controllers/projectController.ts — `createJavaProject` → +# `scaffoldSimpleProject`): +# 1. `window.showQuickPick` → user picks a project type +# 2. `window.showOpenDialog` (folder mode, `openLabel: "Select the project location"`) +# → user picks parent directory +# 3. `window.showInputBox` → user types the project name +# 4. `fse.copy(templates/invisible-project, /)` → scaffold +# 5. `commands.executeCommand("vscode.openFolder", Uri.file(...), openInNewWindow)` +# where openInNewWindow = workspace && !_.isEmpty(workspace.workspaceFolders) +# +# Why a new window does NOT break this test +# ───────────────────────────────────────── +# Because the test runs with a workspace open (`workspace: "../maven"`), +# step 5 opens the new project in a SEPARATE Electron window. The Playwright +# driver is attached via CDP to the original window's renderer process; the +# new window is a separate process the driver never sees. After step 4 +# (the fse.copy) the scaffolded files are already on disk regardless of +# whether step 5 succeeds in opening the new window — so the deterministic +# verifyFile assertions still pass. We deliberately put no UI steps after +# the project creation completes, to avoid any race with the second-window +# spawn momentarily stealing OS focus. +# +# Verification strategy +# ───────────────────── +# `templates/invisible-project/` ships exactly three files (plus an empty +# `lib/` ensure-dir): +# - README.md +# - .vscode/settings.json +# - src/App.java +# We assert two of them (the .vscode settings file and App.java) on disk. +# Notably absent: `.classpath` / `.project` — invisible projects rely on +# `java.import.generatesMetadataFilesAtProjectRoot` (off by default in the +# template), so testing for .classpath would yield a false negative. +# +# Usage: +# npx autotest run test/e2e-plans/java-dep-project-creation.yaml --vsix + +name: "Java Dependency — Create Project" +description: | + Exercises the java.project.create wizard end-to-end on the "No build + tools" path: triggers the command, picks the project type, drives the + folder picker, names the project, and verifies the scaffolded template + files appear on disk. + +setup: + extension: "redhat.java" + vscodeVersion: "stable" + workspace: "../maven" + timeout: 240 + settings: + java.configuration.checkProjectSettingsExclusions: false + workbench.startupEditor: "none" + +steps: + # ── Setup: wait for LS, free sidebar space, focus Java Projects ── + # We open the maven workspace to satisfy step 5's openInNewWindow=true + # branch (see header comment). LS readiness isn't strictly required for + # project creation, but the wait gives the workbench time to settle + # before we drive multiple chained quick-picks / input-boxes. + - id: "ls-ready" + action: "waitForLanguageServer" + # No `verify:` — `waitForLanguageServer` is itself the deterministic + # readiness check. The AFTER screenshot may transiently show + # "Java: Building - 0%" which a strict LLM mis-reads as a failure. + timeout: 180 + + - id: "close-aux-bar" + action: "executeVSCodeCommand workbench.action.closeAuxiliaryBar" + verify: "Auxiliary bar (Chat) closed" + + - id: "collapse-outline" + action: "collapseSidebarSection OUTLINE" + + - id: "collapse-timeline" + action: "collapseSidebarSection TIMELINE" + + - id: "collapse-workspace-root" + action: "collapseWorkspaceRoot" + + - id: "focus-java-projects" + action: "executeVSCodeCommand javaProjectExplorer.focus" + verify: "Java Projects view is focused" + + - id: "wait-tree-load" + action: "wait 3 seconds" + + # ── Step 1: trigger the wizard and pick the project type ── + # Direct command invocation is more reliable than navigating the title- + # bar `$(add)` icon on the Java Projects view, which routes through the + # `_java.project.create.from.javaprojectexplorer` proxy command — same + # underlying handler but extra menu-binding indirection. + - id: "invoke-create-project" + action: "executeVSCodeCommand java.project.create" + + # The project-type quick-pick lists 9 items (controllers/projectController.ts + # `projectTypes`). "No build tools" is index 0. We pick by exact label + # rather than index to keep the plan resilient if the list is re-ordered. + - id: "pick-no-build-tools" + action: "select No build tools option" + + # ── Step 2: drive the folder picker ── + # `window.showOpenDialog` (canSelectFolders: true) is intercepted by + # VS Code's smoke-test driver and re-surfaced as the internal + # simpleFileDialog quick-pick — the same mechanism the classpath plan + # uses for addLibraryFolders. In folder-pick mode, typing a path and + # pressing Enter NAVIGATES INTO the folder; the explicit confirmation + # button (labelled with the dialog's `openLabel`, here "Select the + # project location") is what actually returns the URI to the caller. + # + # We type the workspace root path so the scaffolded project lands + # directly inside the auto-copied worktree — that keeps the verifyFile + # paths simple and the worktree gets cleaned up by autotest at teardown. + - id: "type-project-location" + action: "fillQuickInput ${workspaceFolder}" + + - id: "confirm-project-location" + action: "tryClickButton Select the project location" + + # ── Step 3: name the project ── + # `window.showInputBox` surfaces as the quick-input widget too; + # `fillAnyInput` covers both quick-input and inline-rename widgets so + # it's slightly more robust if VS Code ever changes which surface the + # InputBox API targets. + - id: "type-project-name" + action: "fillAnyInput AutotestNewProject" + + # ── Step 4 + 5: wait for scaffold + new-window spawn ── + # `fse.copy(templates/invisible-project, ...)` is a few-KB synchronous- + # style copy that completes in <500ms, but `vscode.openFolder` triggers + # a second Electron window launch which can briefly steal focus and + # delay file flushing on slower disks. 8s is a comfortable margin. + - id: "wait-scaffold-and-open" + action: "wait 8 seconds" + + # ── Verification: scaffolded template files exist on disk ── + # These two assertions together prove: + # - the wizard reached step 4 (file copy) + # - the basePath (workspace root) and projectName (AutotestNewProject) + # were correctly threaded through showOpenDialog / showInputBox + # + # Verifying directly on disk is the strongest signal we can get: it + # decouples the test from whatever happens with the new-window spawn, + # which Playwright can't observe anyway (different Electron process). + - id: "verify-app-java" + action: "wait 1 seconds" + verifyFile: + path: "${workspaceFolder}/AutotestNewProject/src/App.java" + exists: true + timeout: 15 + + - id: "verify-vscode-settings" + action: "wait 1 seconds" + verifyFile: + path: "${workspaceFolder}/AutotestNewProject/.vscode/settings.json" + exists: true + timeout: 15