-
Notifications
You must be signed in to change notification settings - Fork 97
vscode(feat): Add VS Code extension E2E tests and CI pipeline #8884
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
Open
lambrianmsft
wants to merge
25
commits into
Azure:main
Choose a base branch
from
lambrianmsft:lambrian/vscode_e2e_testing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
515f663
Add VS Code extension E2E tests and CI pipeline
lambrianmsft ab7a273
fix: Pin VS Code version to 1.108.0 for ExTester compatibility
lambrianmsft 4a722e3
Merge main into lambrian/vscode_e2e_testing (resolve pnpm-lock.yaml c…
lambrianmsft 1e4736e
fix: improve e2e test stability — QuickPick wizard handling, writeTes…
lambrianmsft b49654d
fix: CI stability — settings per phase, manifest fallbacks, JS click …
lambrianmsft c038cd6
fix: CI run 2 — driver scope, retry count, conversion settings
lambrianmsft 058502a
fix: wait for extension re-activation after workspace switch
lambrianmsft 48c9012
fix: skip redundant workspace switch, increase CI timeout
lambrianmsft 75f453a
fix: remove 300s blocking wait, fix async interleaving across phases
lambrianmsft da3b8d3
fix: kill VS Code processes on Linux CI between phases (root cause)
lambrianmsft 0a9fabf
fix: replace command palette with Explorer right-click for Open Designer
lambrianmsft fa5d8ab
fix: clean IPC socket files between phases, increase kill wait
lambrianmsft a038848
diag: add comprehensive logging to diagnose code -r failure on Linux CI
lambrianmsft 1844f22
fix: replace code -r with command palette for opening workspaces
lambrianmsft 1918193
fix: replace all openResources (code -r) with Quick Open + command pa…
lambrianmsft da9cef4
fix: dismiss dialogs in openFolderInSession + retry switchToFrame
lambrianmsft 9315f85
fix: bypass ExTester WebView.switchToFrame() with manual Selenium fra…
lambrianmsft 7876f7c
fix: wait for func binary on disk + protect dep validation from dismi…
lambrianmsft 28dd2aa
fix: chmod +x on downloaded runtime binaries (func/dotnet/node)
lambrianmsft 18d53ae
fix: wait for extension dependency validation before opening designer
lambrianmsft d11c5bd
fix: don't falsely match outer webview frame body as #root
lambrianmsft 54f6da2
revert: restore #root,body selector for VS Code 1.108.0 compatibility
lambrianmsft 608c99c
fix: cache runtime deps, dismiss GitHub 403, wait for design-time API
lambrianmsft 6fee35d
fix: guard authData null access + remove subscription keys from local…
lambrianmsft a282239
fix: set WORKFLOWS_SUBSCRIPTION_ID='' instead of deleting it
lambrianmsft 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
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,94 @@ | ||
| name: VS Code Extension E2E Tests | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main, dev/*, hotfix/*] | ||
| pull_request: | ||
| branches: [main, dev/*, hotfix/*] | ||
|
|
||
| concurrency: | ||
| group: vscode-e2e-${{ github.head_ref || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| vscode-e2e: | ||
| timeout-minutes: 90 | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Cache turbo build setup | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: .turbo | ||
| key: ${{ runner.os }}-turbo-${{ github.sha }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-turbo- | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20.x | ||
|
|
||
| - name: Setup pnpm | ||
| uses: pnpm/action-setup@v3 | ||
| with: | ||
| version: 9.1.3 | ||
| run_install: | | ||
| - recursive: true | ||
| args: [--frozen-lockfile, --strict-peer-dependencies] | ||
|
|
||
| - name: Build extension | ||
| run: pnpm turbo run build:extension --cache-dir=.turbo | ||
|
|
||
| - name: Compile E2E tests | ||
| working-directory: apps/vs-code-designer | ||
| run: npx tsup --config tsup.e2e.test.config.ts | ||
|
|
||
| - name: Install system dependencies for virtual display | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y xvfb libgbm-dev libgtk-3-0 libnss3 libasound2t64 libxss1 libatk-bridge2.0-0 libatk1.0-0 | ||
|
|
||
| # Cache the auto-downloaded runtime dependencies (func, dotnet, node) | ||
| # so subsequent runs don't re-download ~500MB each time. | ||
| - name: Cache Logic Apps runtime dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.azurelogicapps/dependencies | ||
| key: la-runtime-deps-${{ runner.os }}-v1 | ||
| restore-keys: | | ||
| la-runtime-deps-${{ runner.os }}- | ||
| # Save even when tests fail so deps are available on next run | ||
| save-always: true | ||
|
|
||
| - name: Run VS Code Extension E2E tests | ||
| run: xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" node apps/vs-code-designer/src/test/ui/run-e2e.js | ||
| env: | ||
| # Run all phases (4.1 workspace creation through 4.7 smoke tests) | ||
| E2E_MODE: full | ||
| # Increase Node memory for CI | ||
| NODE_OPTIONS: --max-old-space-size=4096 | ||
| # Set TEMP so screenshot paths are predictable on Linux | ||
| # (process.env.TEMP is undefined on Ubuntu, causing fallback to cwd) | ||
| TEMP: ${{ runner.temp }} | ||
|
|
||
| # Screenshots are written to $TEMP/test-resources/screenshots/ by both: | ||
| # - Explicit test screenshots (e.g., inlineJS-after-request-trigger.png) | ||
| # - ExTester auto-failure screenshots (captured on test failure) | ||
| # Upload ALL screenshots as artifacts so they're available for debugging | ||
| # after the pipeline finishes, even if the runner is recycled. | ||
| - name: Upload test screenshots (always) | ||
| uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: vscode-e2e-screenshots | ||
| path: | | ||
| ${{ runner.temp }}/test-resources/screenshots/ | ||
| test-resources/screenshots/ | ||
| if-no-files-found: ignore | ||
| retention-days: 30 |
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
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
193 changes: 193 additions & 0 deletions
193
apps/vs-code-designer/.github/copilot-skills/vscode-e2e-testing.md
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,193 @@ | ||
| # Skill: VS Code Extension E2E Testing for Logic Apps | ||
|
|
||
| ## Overview | ||
| E2E tests for the Azure Logic Apps VS Code extension using `@vscode/test-cli` + Mocha TDD, running inside a real VS Code instance with the extension loaded. | ||
|
|
||
| ## Test Location & Structure | ||
| - **Test root**: `apps/vs-code-designer/src/test/e2e/integration/` | ||
| - **Config**: `apps/vs-code-designer/.vscode-test.mjs` — two profiles: | ||
| - `unitTests`: all tests, `--disable-extensions`, 60s timeout | ||
| - `integrationTests`: integration folder only, extensions enabled, 120s timeout | ||
| - **TypeScript config**: `apps/vs-code-designer/tsconfig.e2e.json` → `module: commonjs`, `target: ES2022`, `outDir: ./out/test/e2e`, `rootDir: ./src/test/e2e` | ||
| - **Test workspace**: `apps/vs-code-designer/e2e/test-workspace/` (has `package.json`, `.vscode/`, `Workflows/TestWorkflow/workflow.json`) | ||
|
|
||
| ## Commands | ||
| ```bash | ||
| # Compile tests | ||
| pnpm run test:e2e-cli:compile | ||
|
|
||
| # Run integration tests (extensions loaded) | ||
| pnpm run test:e2e-cli -- --label integrationTests | ||
|
|
||
| # Run all e2e tests | ||
| pnpm run test:e2e-cli | ||
| ``` | ||
|
|
||
| ## Test Framework Rules | ||
| - **Mocha TDD style**: Use `suite`/`test`, NEVER `describe`/`it` | ||
| - **Assertions**: `import * as assert from 'assert'` | ||
| - **VS Code API**: `import * as vscode from 'vscode'` | ||
| - **Timeouts**: Set via `this.timeout(ms)` on suite/test functions (use `function()` not arrow) | ||
|
|
||
| ## Extension Facts | ||
| - **Extension ID**: `ms-azuretools.vscode-azurelogicapps` | ||
| - **CRITICAL**: `vscode.extensions.getExtension(EXTENSION_ID)` returns `undefined` in dev/test because the dev `package.json` lacks the `engines` field. Always use defensive checks: | ||
| ```typescript | ||
| const ext = vscode.extensions.getExtension(EXTENSION_ID); | ||
| if (ext) { /* test extension-specific things */ } | ||
| else { assert.ok(true, 'Extension not found by production ID in test env'); } | ||
| ``` | ||
| - **`activate()` returns `void`**: No exported API. Interact only via `vscode.commands.executeCommand()`. | ||
| - **Commands are still registered** even when `getExtension` returns undefined — test them via `vscode.commands.getCommands(true)`. | ||
|
|
||
| ## Key Extension Commands | ||
| | Command | Purpose | | ||
| |---------|---------| | ||
| | `azureLogicAppsStandard.createWorkspace` | Opens workspace creation webview (same as clicking "Yes" on conversion dialog) | | ||
| | `azureLogicAppsStandard.createProject` | Creates a new Logic App project | | ||
| | `azureLogicAppsStandard.createWorkflow` | Creates a new workflow | | ||
| | `azureLogicAppsStandard.openDesigner` | Opens the workflow designer | | ||
|
|
||
| ## Webview Detection | ||
| - **`instanceof vscode.TabInputWebview` is BROKEN** in test env. Use duck-typing: | ||
| ```typescript | ||
| function getWebviewTabs(viewType?: string): vscode.Tab[] { | ||
| const tabs: vscode.Tab[] = []; | ||
| for (const group of vscode.window.tabGroups.all) { | ||
| for (const tab of group.tabs) { | ||
| const input = tab.input as any; | ||
| if (input && typeof input.viewType === 'string') { | ||
| if (!viewType || input.viewType === viewType) { | ||
| tabs.push(tab); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return tabs; | ||
| } | ||
| ``` | ||
| - **Timing**: After `executeCommand`, wait 2000-3000ms before checking `tabGroups`. | ||
| - **Close tabs**: `await vscode.window.tabGroups.close(tab)` — wait 500ms after. | ||
|
|
||
| ## Webview Message Protocol | ||
| The extension's workspace creation webview (`viewType: 'CreateWorkspace'`) uses this message flow: | ||
|
|
||
| | Step | Direction | Command | Payload | | ||
| |------|-----------|---------|---------| | ||
| | 1 | React→Ext | `initialize` | `{}` | | ||
| | 2 | Ext→React | `initialize-frame` | `{ apiVersion, project, separator, platform }` | | ||
| | 3 | React→Ext | `select-folder` | `{}` | | ||
| | 4 | Ext→React | `update-workspace-path` | `{ targetDirectory: { fsPath, path } }` | | ||
| | 5 | React→Ext | `validatePath` | `{ path, type: 'workspace_folder' }` | | ||
| | 6 | Ext→React | `workspace-existence-result` | `{ project, workspacePath, exists, type }` | | ||
| | 7 | React→Ext | `createWorkspace` or `createWorkspaceStructure` | `IWebviewProjectContext` | | ||
| | 8 | Extension | Creates files, disposes panel, calls `vscode.openFolder` | — | | ||
|
|
||
| ## IWebviewProjectContext Interface | ||
| ```typescript | ||
| { | ||
| workspaceName: string; | ||
| workspaceProjectPath: { fsPath: string; path: string }; | ||
| logicAppName: string; | ||
| logicAppType: 'logicApp' | 'customCode' | 'rulesEngine'; | ||
| projectType: string; | ||
| workflowName: string; | ||
| workflowType: 'Stateful-Codeless' | 'Stateless-Codeless' | 'Agent-Codeless'; | ||
| targetFramework: 'net472' | 'net8'; | ||
| functionFolderName?: string; // customCode only | ||
| functionName?: string; // customCode only | ||
| functionNamespace?: string; // customCode only | ||
| shouldCreateLogicAppProject: boolean; | ||
| } | ||
| ``` | ||
|
|
||
| ## Conversion Flow (convertToWorkspace.ts) | ||
| Called during `activate()`. Three decision branches: | ||
| - **Path A**: `.code-workspace` file exists but not opened → modal "Open existing workspace?" | ||
| - **Path B**: No `.code-workspace` file → modal "Create workspace?" → if Yes → opens `CreateWorkspace` webview | ||
| - **Path C**: Already in multi-root workspace → return true, nothing to do | ||
|
|
||
| ## Common Gotchas & Fixes | ||
| | Issue | Fix | | ||
| |-------|-----| | ||
| | `Buffer.from()` not assignable to `Uint8Array` | Use `new TextEncoder().encode()` | | ||
| | `.code-workspace` detected as language `jsonc` | Assert `languageId` is `json` OR `jsonc` | | ||
| | `instanceof TabInputWebview` fails | Duck-type: `typeof input.viewType === 'string'` | | ||
| | Extension not found by ID | Defensive `if (ext)` with fallback assertion | | ||
| | Webview tab not in `tabGroups` | `await sleep(2000-3000)` after command execution | | ||
| | `this.timeout()` in arrow function | Use `function()` syntax for Mocha context | | ||
|
|
||
| ## Test File Template | ||
| ```typescript | ||
| import * as assert from 'assert'; | ||
| import * as vscode from 'vscode'; | ||
|
|
||
| const EXTENSION_ID = 'ms-azuretools.vscode-azurelogicapps'; | ||
|
|
||
| function sleep(ms: number): Promise<void> { | ||
| return new Promise((resolve) => setTimeout(resolve, ms)); | ||
| } | ||
|
|
||
| suite('Feature Name', () => { | ||
| const disposables: vscode.Disposable[] = []; | ||
|
|
||
| suiteSetup(async function () { | ||
| this.timeout(30000); | ||
| const ext = vscode.extensions.getExtension(EXTENSION_ID); | ||
| if (ext && !ext.isActive) { | ||
| try { await ext.activate(); } catch { /* may not fully activate */ } | ||
| } | ||
| await sleep(2000); | ||
| }); | ||
|
|
||
| teardown(async function () { | ||
| this.timeout(15000); | ||
| await vscode.commands.executeCommand('workbench.action.closeAllEditors'); | ||
| for (const d of disposables) d.dispose(); | ||
| disposables.length = 0; | ||
| await sleep(500); | ||
| }); | ||
|
|
||
| test('Example test', async function () { | ||
| this.timeout(20000); | ||
| // Execute real extension command | ||
| try { | ||
| await vscode.commands.executeCommand('azureLogicAppsStandard.someCommand'); | ||
| } catch { /* may fail if assets missing */ } | ||
| await sleep(2000); | ||
| // Assert results via VS Code APIs | ||
| }); | ||
| }); | ||
| ``` | ||
|
|
||
| ## Existing Test Files (204 tests total, all passing) | ||
| | File | Tests | Coverage | | ||
| |------|-------|----------| | ||
| | `extension.test.ts` | 3 | Activation basics | | ||
| | `commands.test.ts` | 4 | Command registration | | ||
| | `workflow.test.ts` | 5 | Workflow detection | | ||
| | `designer.test.ts` | 4 | Designer panel basics | | ||
| | `createWorkspace.test.ts` | 10+ | Workspace creation | | ||
| | `projectOutsideWorkspace.test.ts` | 22 | Projects outside workspace | | ||
| | `workspaceConfigurations.test.ts` | 34 | Workspace config | | ||
| | `debugging.test.ts` | 33 | Debugging functionality | | ||
| | `designerOpens.test.ts` | 30 | Designer opening | | ||
| | `nodeLoading.test.ts` | 37 | Action/trigger node loading | | ||
| | `workspaceConversion.test.ts` | 27 | Workspace conversion | | ||
|
|
||
| ## Philosophy | ||
| - Tests must exercise the **real extension** — execute actual commands, detect real webview panels | ||
| - **No manual file creation** simulating what the extension does | ||
| - Extension host tests can execute commands and detect panels but **cannot interact with webview DOM** (typing/clicking inside webview). That requires Playwright against Electron. | ||
| - Use defensive assertions: if the extension doesn't fully load, test the pattern/convention rather than hard-failing | ||
|
|
||
| ## Related ExTester UI Suite (apps/vs-code-designer/src/test/ui) | ||
| - This skill file covers `@vscode/test-cli` extension-host tests; interactive webview DOM coverage is implemented in the ExTester UI suite. | ||
| - Phase 4.2 now runs **only `designerActions.test.ts`** (2 focused tests, ~2 min, 100% reliability over 5 runs). | ||
| - Test 1: Standard workflow — open designer → add Request trigger → assert node on canvas | ||
| - Test 2: CustomCode workflow — open designer → add Compose action → assert node on canvas | ||
| - Each test uses `assert.ok()` at every step (designer opened, panel opened, search results, node added). | ||
| - All waits are detection-based (poll for DOM changes). No static sleeps. | ||
| - Key reliability fixes: command palette retries up to 5x on "not found", stale element retry on React Flow re-renders, Selenium Actions API for React-compatible clicks. | ||
| - `designerOpen.test.ts` is no longer included in Phase 4.2 — designer opening is tested implicitly by each action test. | ||
| - Runtime dependency paths (`funcCoreToolsBinaryPath`, `dotnetBinaryPath`, `nodeJsBinaryPath`) are configured in `run-e2e.js` test settings pointing to `~/.azurelogicapps/dependencies/`. |
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,41 @@ | ||
| import { defineConfig } from '@vscode/test-cli'; | ||
| import * as path from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
|
|
||
| export default defineConfig([ | ||
| { | ||
| label: 'unitTests', | ||
| files: 'out/test/e2e/**/*.test.js', | ||
| version: 'stable', | ||
| workspaceFolder: path.join(__dirname, 'e2e', 'test-workspace'), | ||
| mocha: { | ||
| ui: 'tdd', | ||
| timeout: 60000, | ||
| }, | ||
| launchArgs: [ | ||
| '--disable-extensions', // Disable other extensions to speed up tests | ||
| '--user-data-dir', path.join(__dirname, '.vscode-test', 'user-data'), | ||
| '--extensions-dir', path.join(__dirname, '.vscode-test', 'extensions'), | ||
| '--disable-gpu', // Helps with stability in CI | ||
| '--disable-updates', // Prevent update checks | ||
| ], | ||
| }, | ||
| { | ||
| label: 'integrationTests', | ||
| files: 'out/test/e2e/integration/**/*.test.js', | ||
| version: 'stable', | ||
| workspaceFolder: path.join(__dirname, 'e2e', 'test-workspace'), | ||
| mocha: { | ||
| ui: 'tdd', | ||
| timeout: 120000, | ||
| }, | ||
| launchArgs: [ | ||
| '--user-data-dir', path.join(__dirname, '.vscode-test', 'user-data'), | ||
| '--extensions-dir', path.join(__dirname, '.vscode-test', 'extensions'), | ||
| '--disable-gpu', | ||
| '--disable-updates', | ||
| ], | ||
| }, | ||
| ]); | ||
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.vscode-test.mjsdepends on@vscode/test-cli, but this repo/package doesn’t declare it as a dependency (and it isn’t present inpnpm-lock.yaml). This will fail at runtime in CI/dev. Add@vscode/test-clito the appropriatepackage.json(and lockfile) or switch this config to the existing@vscode/test-electrontooling already in use.