diff --git a/.github/workflows/e2eUI.yml b/.github/workflows/e2eUI.yml new file mode 100644 index 00000000..96855ee7 --- /dev/null +++ b/.github/workflows/e2eUI.yml @@ -0,0 +1,331 @@ +name: E2E UI Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +# Split-pipeline E2E UI workflow. +# +# lint → tslint + checkstyle (ubuntu, OS-agnostic) +# discover-plans → emits a matrix of test-plan basenames +# +# build-linux ─┐ +# e2e-linux (×plan) ┤ +# ├──→ analyze → unified summary covering both OSes +# build-windows ─┤ +# e2e-windows (×plan)┘ +# +# Per-OS pipelines run completely independently: Linux e2e jobs do NOT +# wait for the Windows VSIX build (and vice versa), so a slow Windows +# build cannot delay the start of Linux e2e plans. Each matrix cell +# surfaces as its own PR check, so failures are visible without an +# extra gate job. +# +# Inspired by vscode-java-pack/.github/workflows/e2e-autotest.yml. + +jobs: + # ── Lint + Checkstyle (OS-agnostic) ───────────────────── + lint: + name: Lint & Checkstyle + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node.js modules + run: npm install + + - name: Lint + run: npm run tslint + + - name: Checkstyle + working-directory: ./jdtls.ext + run: ./mvnw checkstyle:check + + # ── Discover test plans ───────────────────────────────── + discover-plans: + name: Discover E2E Plans + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.scan.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Scan test plans + id: scan + shell: bash + run: | + plans=$(ls test/e2e-plans/*.yaml | xargs -n1 basename | sed 's/\.yaml$//' | jq -R . | jq -sc .) + echo "matrix=$plans" >> "$GITHUB_OUTPUT" + echo "Found plans: $plans" + + # ── Build VSIX (Linux) ────────────────────────────────── + build-linux: + name: Build VSIX (Linux) + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node.js modules + run: npm install + + - name: Install VSCE + run: npm install -g @vscode/vsce + + - name: Build OSGi bundle + run: npm run build-server + + - name: Build VSIX file + run: vsce package -o vscode-java-dependency.vsix + + - name: Upload VSIX artifact + uses: actions/upload-artifact@v4 + with: + name: vsix-linux + path: vscode-java-dependency.vsix + retention-days: 1 + + # ── Build VSIX (Windows) ──────────────────────────────── + build-windows: + name: Build VSIX (Windows) + runs-on: windows-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install Node.js modules + run: npm install + + - name: Install VSCE + run: npm install -g @vscode/vsce + + - name: Build OSGi bundle + run: npm run build-server + + - name: Build VSIX file + run: vsce package -o vscode-java-dependency.vsix + + - name: Upload VSIX artifact + uses: actions/upload-artifact@v4 + with: + name: vsix-windows + path: vscode-java-dependency.vsix + retention-days: 1 + + # ── E2E plans (Linux) — depends only on Linux build ───── + e2e-linux: + name: E2E Linux (${{ matrix.plan }}) + needs: [ build-linux, discover-plans ] + runs-on: ubuntu-latest + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + plan: ${{ fromJson(needs.discover-plans.outputs.matrix) }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Build Environment (Xvfb) + run: | + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + # Use 1920x1080 so the Java Projects view (rendered inside the Explorer + # sidebar) gets enough vertical space. With 1024x768 the sticky + # pane-header overlapped tree rows and intercepted click events. + sudo /usr/bin/Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 & + sleep 3 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup autotest + run: npm install -g @vscjava/vscode-autotest + + - name: Download VSIX artifact + uses: actions/download-artifact@v4 + with: + name: vsix-linux + path: . + + - name: E2E Test — ${{ matrix.plan }} + shell: bash + env: + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }} + run: | + DISPLAY=:99 autotest run "test/e2e-plans/${{ matrix.plan }}.yaml" \ + --vsix "$(pwd)/vscode-java-dependency.vsix" \ + --output "test-results/${{ matrix.plan }}" + + - name: Upload test results + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: e2e-results-linux-${{ matrix.plan }} + path: test-results/ + retention-days: 7 + + # ── E2E plans (Windows) — depends only on Windows build ─ + e2e-windows: + name: E2E Windows (${{ matrix.plan }}) + needs: [ build-windows, discover-plans ] + runs-on: windows-latest + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + plan: ${{ fromJson(needs.discover-plans.outputs.matrix) }} + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup autotest + run: npm install -g @vscjava/vscode-autotest + + - name: Download VSIX artifact + uses: actions/download-artifact@v4 + with: + name: vsix-windows + path: . + + - name: E2E Test — ${{ matrix.plan }} + shell: pwsh + env: + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }} + run: | + autotest run "test/e2e-plans/${{ matrix.plan }}.yaml" --vsix "$((Get-Location).Path)\vscode-java-dependency.vsix" --output "test-results\${{ matrix.plan }}" + + - name: Upload test results + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: e2e-results-windows-${{ matrix.plan }} + path: test-results/ + retention-days: 7 + + # ── Unified analysis across both OSes ─────────────────── + analyze: + name: E2E Summary + needs: [ e2e-linux, e2e-windows ] + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup autotest + run: npm install -g @vscjava/vscode-autotest + + - name: Download all plan results + uses: actions/download-artifact@v4 + with: + pattern: e2e-results-* + path: all-results + merge-multiple: false + + - name: Organize results (prefix each plan by OS) + shell: bash + run: | + mkdir -p test-results + for dir in all-results/e2e-results-linux-*/; do + [ -d "$dir" ] || continue + find "$dir" -name "results.json" -exec dirname {} \; | while read d; do + name=$(basename "$d") + mkdir -p "test-results/linux-$name" + cp -r "$d"/. "test-results/linux-$name"/ + done + done + for dir in all-results/e2e-results-windows-*/; do + [ -d "$dir" ] || continue + find "$dir" -name "results.json" -exec dirname {} \; | while read d; do + name=$(basename "$d") + mkdir -p "test-results/windows-$name" + cp -r "$d"/. "test-results/windows-$name"/ + done + done + echo "Organized plan result directories:" + ls test-results/ || true + + - name: Analyze results + env: + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }} + run: autotest analyze test-results --output test-results + + - name: Write Job Summary + if: always() + shell: bash + run: | + if [ -f test-results/summary.md ]; then + cat test-results/summary.md >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload aggregate summary + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-aggregate-summary + path: test-results/summary.md + retention-days: 30 diff --git a/.github/workflows/linuxUI.yml b/.github/workflows/linuxUI.yml deleted file mode 100644 index 1ee20df0..00000000 --- a/.github/workflows/linuxUI.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - linuxUI: - name: Linux-UI - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - name: Setup Build Environment - run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 - # Use 1920x1080 so the Java Projects view (rendered inside the Explorer - # sidebar) gets enough vertical space. With 1024x768 the sticky - # pane-header overlapped tree rows and intercepted click events. - sudo /usr/bin/Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 & - sleep 3 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - - name: Setup Node.js environment - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install Node.js modules - run: npm install - - - name: Install VSCE - run: npm install -g @vscode/vsce - - - name: Build OSGi bundle - run: npm run build-server - - - name: Build VSIX file - run: vsce package -o vscode-java-dependency.vsix - - - name: Setup autotest - run: npm install -g @vscjava/vscode-autotest - - - name: E2E Test (autotest) - env: - AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} - AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} - AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }} - run: | - DISPLAY=:99 autotest run-all test/e2e-plans \ - --vsix $(pwd)/vscode-java-dependency.vsix \ - --output test-results - - - name: Upload test results - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: e2e-results-linux - path: test-results/ - retention-days: 7 - - - name: Write Job Summary - if: always() - run: | - if [ -f test-results/summary.md ]; then - cat test-results/summary.md >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.github/workflows/windowsUI.yml b/.github/workflows/windowsUI.yml deleted file mode 100644 index c92c6640..00000000 --- a/.github/workflows/windowsUI.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - windowsUI: - name: Windows-UI - runs-on: windows-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - - name: Setup Node.js environment - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install Node.js modules - run: npm install - - - name: Install VSCE - run: npm install -g @vscode/vsce - - - name: Lint - run: npm run tslint - - - name: Checkstyle - working-directory: .\jdtls.ext - run: .\mvnw.cmd checkstyle:check - - - name: Build OSGi bundle - run: npm run build-server - - - name: Build VSIX file - run: vsce package -o vscode-java-dependency.vsix - - - name: Setup autotest - run: npm install -g @vscjava/vscode-autotest - - - name: E2E Test (autotest) - env: - AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} - AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} - AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }} - run: | - autotest run-all test/e2e-plans --vsix "$((Get-Location).Path)\vscode-java-dependency.vsix" --output test-results - - - name: Upload test results - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: e2e-results-windows - path: test-results/ - retention-days: 7 - - - name: Write Job Summary - if: always() - shell: bash - run: | - if [ -f test-results/summary.md ]; then - cat test-results/summary.md >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/test/e2e-plans/java-dep-build-lifecycle.yaml b/test/e2e-plans/java-dep-build-lifecycle.yaml index ab650c0a..b0f6df66 100644 --- a/test/e2e-plans/java-dep-build-lifecycle.yaml +++ b/test/e2e-plans/java-dep-build-lifecycle.yaml @@ -12,10 +12,8 @@ # - java.project.build.project (Build Project — project context menu) # - java.project.rebuild (Rebuild Project — project context menu) # - java.project.reloadProjectFromActiveFile (Reload Project — pom.xml editor title) -# -# Note: java.project.clean.workspace is intentionally omitted from this plan -# because the JDT.LS clean command triggers a VS Code window reload, which -# tears down the autotest browser session. +# - java.project.update (Reload Project — Maven submenu on project context menu) +# - java.project.clean.workspace (Clean Workspace — view-title overflow; dialog cancelled to avoid VS Code reload) # # Verification strategy # ───────────────────── @@ -165,3 +163,51 @@ steps: - id: "wait-reload-project" action: "waitForLanguageServer" timeout: 180 + + # ── Test 6: Reload Project (Maven submenu on project context menu) ── + # `java.project.update` lives in the `javaProject.maven` submenu under the + # project context menu's `9_configuration@10` group. Real users right-click + # the Maven project node → hover the "Maven" submenu → click "Reload + # Project". This is the only UI surface for this command (no command + # palette entry, no toolbar button). + - id: "close-pom-editors" + action: "run command View: Close All Editors" + + - id: "focus-java-projects-reload" + action: "executeVSCodeCommand javaProjectExplorer.focus" + waitBefore: 1 + + - id: "click-project-update" + action: "click my-app tree item" + waitBefore: 1 + + - id: "context-update-maven" + action: 'contextMenuSubmenu my-app Maven "Reload Project"' + + - id: "wait-update-project" + action: "waitForLanguageServer" + timeout: 180 + + # ── Test 7: Clean Workspace (overflow menu, dialog cancelled if shown) ── + # `java.project.clean.workspace` is contributed to the Java Projects view + # title-bar `overflow_20@10` group. When clicked it forwards to JDT.LS's + # `java.clean.workspace`. Depending on the redhat.java / JDT.LS version + # this may or may not raise a modal warning dialog ("…delete workspace + # cache and restart?") before doing the work. We use a *tolerant* dialog + # cancel (`tryClickDialogButton`) so the step passes whether or not the + # dialog appears — when it does appear we cancel to avoid the destructive + # VS Code reload; when it does not we proceed straight to the LS settle + # check. The primary coverage signal is the overflow-menu mount + click, + # which `trigger-clean-workspace` already exercises with a deterministic + # `clickViewTitleAction`. + - id: "trigger-clean-workspace" + action: 'clickViewTitleAction "Java Projects" "Clean Workspace"' + + - id: "cancel-clean-dialog" + action: "tryClickDialogButton Cancel" + # No `verify:` — tolerant action: clicks Cancel if dialog appears, else + # silently no-ops. `trigger-clean-workspace` is the deterministic signal. + + - id: "wait-clean-settle" + action: "waitForLanguageServer" + timeout: 60 diff --git a/test/e2e-plans/java-dep-new-types.yaml b/test/e2e-plans/java-dep-new-types.yaml index 1477737c..4e717895 100644 --- a/test/e2e-plans/java-dep-new-types.yaml +++ b/test/e2e-plans/java-dep-new-types.yaml @@ -7,6 +7,7 @@ # Commands exercised (each invoked through the New... quick-pick on a node): # - java.view.package.newJavaInterface (Interface) # - java.view.package.newJavaEnum (Enum) +# - java.view.package.newJavaRecord (Record — requires Java 16+) # - java.view.package.newJavaAnnotation (Annotation) # - java.view.package.newJavaAbstractClass (Abstract Class) # - java.view.package.newFile ("File" option) @@ -31,7 +32,7 @@ name: "Java Dependency — New File Types" description: | Tests all the "New ..." quick-pick options in the Java Projects view that are not already covered by java-dep-file-operations.yaml: - Interface / Enum / Annotation / Abstract Class / File / Folder. + Interface / Enum / Record / Annotation / Abstract Class / File / Folder. setup: extension: "redhat.java" @@ -190,7 +191,9 @@ steps: action: "executeVSCodeCommand javaProjectExplorer.focus" waitBefore: 1 - # ── Test 4: New Abstract Class ── + # ── Test 4: New Record (Java 16+; fixture pom uses Java 17) ── + # The Record option is only shown when the project source level is >= 16, + # see JavaType.getDisplayNames(..., includeRecord) in src/explorerCommands/new.ts. - id: "click-project-4" action: "click my-app tree item" waitBefore: 1 @@ -198,19 +201,19 @@ steps: - id: "trigger-new-4" action: "clickTreeItemAction my-app New..." - - id: "select-abstract-class" - action: "select Abstract Class option" + - id: "select-record" + action: "select Record option" - id: "select-source-folder-4" action: "select src/main/java option" - - id: "enter-abstract-name" - action: "fillQuickInput MyAbstract" + - id: "enter-record-name" + action: "fillQuickInput MyRecord" - - id: "verify-abstract-tab" + - id: "verify-record-tab" action: "wait 2 seconds" verifyEditorTab: - title: "MyAbstract.java" + title: "MyRecord.java" timeout: 20 - id: "save-4" @@ -229,9 +232,7 @@ steps: action: "executeVSCodeCommand javaProjectExplorer.focus" waitBefore: 1 - # ── Test 5: New File (plain non-Java file via "File" option) ── - # The "File" entry routes to `java.view.package.newFile` and writes the - # file under the project root (the node we triggered from). + # ── Test 5: New Abstract Class ── - id: "click-project-5" action: "click my-app tree item" waitBefore: 1 @@ -239,6 +240,47 @@ steps: - id: "trigger-new-5" action: "clickTreeItemAction my-app New..." + - id: "select-abstract-class" + action: "select Abstract Class option" + + - id: "select-source-folder-5" + action: "select src/main/java option" + + - id: "enter-abstract-name" + action: "fillQuickInput MyAbstract" + + - id: "verify-abstract-tab" + action: "wait 2 seconds" + verifyEditorTab: + title: "MyAbstract.java" + timeout: 20 + + - id: "save-5" + action: "executeVSCodeCommand workbench.action.files.saveAll" + + - id: "close-editors-5" + action: "run command View: Close All Editors" + + - id: "collapse-tree-5" + action: 'clickViewTitleAction "Java Projects" "Collapse All"' + + - id: "collapse-workspace-root-6" + action: "collapseWorkspaceRoot" + + - id: "focus-java-projects-6" + action: "executeVSCodeCommand javaProjectExplorer.focus" + waitBefore: 1 + + # ── Test 6: New File (plain non-Java file via "File" option) ── + # The "File" entry routes to `java.view.package.newFile` and writes the + # file under the project root (the node we triggered from). + - id: "click-project-6" + action: "click my-app tree item" + waitBefore: 1 + + - id: "trigger-new-6" + action: "clickTreeItemAction my-app New..." + - id: "select-file" action: "select File option" @@ -251,30 +293,30 @@ steps: title: "notes.txt" timeout: 20 - - id: "save-5" + - id: "save-6" action: "executeVSCodeCommand workbench.action.files.saveAll" - - id: "close-editors-5" + - id: "close-editors-6" action: "run command View: Close All Editors" - - id: "collapse-tree-5" + - id: "collapse-tree-6" action: 'clickViewTitleAction "Java Projects" "Collapse All"' - - id: "collapse-workspace-root-6" + - id: "collapse-workspace-root-7" action: "collapseWorkspaceRoot" - - id: "focus-java-projects-6" + - id: "focus-java-projects-7" action: "executeVSCodeCommand javaProjectExplorer.focus" waitBefore: 1 - # ── Test 6: New Folder ── + # ── Test 7: New Folder ── # Folder creation has no editor side-effect — verify by checking the new # folder exists on disk under the workspace root. - - id: "click-project-6" + - id: "click-project-7" action: "click my-app tree item" waitBefore: 1 - - id: "trigger-new-6" + - id: "trigger-new-7" action: "clickTreeItemAction my-app New..." - id: "select-folder"