From 1fe7a3cbc6d062aeb6041814ecef9d22cb1fc16b Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 21 Jan 2026 12:15:24 -0500 Subject: [PATCH 1/5] improve vscode e2e --- packages/typespec-vscode/package.json | 2 +- .../test/extension/common/common-steps.ts | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/typespec-vscode/package.json b/packages/typespec-vscode/package.json index 7fb914d9b4f..6f55acde21a 100644 --- a/packages/typespec-vscode/package.json +++ b/packages/typespec-vscode/package.json @@ -277,7 +277,7 @@ "package-vsix": "vsce package", "deploy": "vsce publish", "open-in-browser": "vscode-test-web --extensionDevelopmentPath=. .", - "test:e2e:skip": "pnpm test:web && pnpm test:extension", + "test:e2e": "pnpm test:web && pnpm test:extension", "test:web": "vscode-test-web --quality stable --extensionDevelopmentPath=. --headless --extensionTestsPath=dist/test/web/suite.js ./test/web/data", "test:extension": "vitest run --root test/extension" }, diff --git a/packages/typespec-vscode/test/extension/common/common-steps.ts b/packages/typespec-vscode/test/extension/common/common-steps.ts index 38f39126221..a14775a43cd 100644 --- a/packages/typespec-vscode/test/extension/common/common-steps.ts +++ b/packages/typespec-vscode/test/extension/common/common-steps.ts @@ -1,8 +1,9 @@ import { readdirSync } from "fs"; -import { rm } from "fs/promises"; import fs, { rmSync, writeFileSync } from "node:fs"; +import { readdir } from "node:fs/promises"; import path from "node:path"; import { Locator, Page } from "playwright"; +import { expect } from "vitest"; import { RunOptions, runOrExit } from "../../../../internal-build-utils/dist/src/index.js"; import { CaseScreenshot, npxCmd, repoRoot, retry, tempDir } from "./utils"; @@ -32,18 +33,19 @@ export async function preContrastResult( /** * Results comparison - * @param res List of expected files + * @param exected List of expected files * @param dir The directory to be compared needs to be converted into an absolute path using path.resolve */ -export async function contrastResult(res: string[], dir: string, cs: CaseScreenshot) { - let resLength = 0; - if (fs.existsSync(dir)) { - resLength = fs.readdirSync(dir).length; - await rm(cs.caseDir, { recursive: true }); - } - if (resLength !== res.length) { - throw new Error("Failed to matches all files"); - } +export async function contrastResult(exected: string[], dir: string, cs: CaseScreenshot) { + // let resLength = 0; + // if (fs.existsSync(dir)) { + const results = await readdir(dir); + // await rm(cs.caseDir, { recursive: true }); + // } + expect(results).toEqual(exected); + // if (resLength !== res.length) { + // throw new Error("Failed to matches all files"); + // } } /** From db46f81b49cc3b0aa8677fbce87606b09e7f0980 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 21 Jan 2026 12:53:10 -0500 Subject: [PATCH 2/5] better name --- .../test/extension/common/common-steps.ts | 9 +-------- .../test/extension/create-typespec.test.ts | 8 ++++++-- .../typespec-vscode/test/extension/emit-typespec.test.ts | 4 ++-- .../test/extension/import-typespec.test.ts | 4 ++-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/typespec-vscode/test/extension/common/common-steps.ts b/packages/typespec-vscode/test/extension/common/common-steps.ts index a14775a43cd..58139ee2b92 100644 --- a/packages/typespec-vscode/test/extension/common/common-steps.ts +++ b/packages/typespec-vscode/test/extension/common/common-steps.ts @@ -36,16 +36,9 @@ export async function preContrastResult( * @param exected List of expected files * @param dir The directory to be compared needs to be converted into an absolute path using path.resolve */ -export async function contrastResult(exected: string[], dir: string, cs: CaseScreenshot) { - // let resLength = 0; - // if (fs.existsSync(dir)) { +export async function expectFilesInDir(exected: string[], dir: string) { const results = await readdir(dir); - // await rm(cs.caseDir, { recursive: true }); - // } expect(results).toEqual(exected); - // if (resLength !== res.length) { - // throw new Error("Failed to matches all files"); - // } } /** diff --git a/packages/typespec-vscode/test/extension/create-typespec.test.ts b/packages/typespec-vscode/test/extension/create-typespec.test.ts index 8cbcd06d118..cdbc7afefe5 100644 --- a/packages/typespec-vscode/test/extension/create-typespec.test.ts +++ b/packages/typespec-vscode/test/extension/create-typespec.test.ts @@ -2,7 +2,11 @@ import { mkdir } from "fs/promises"; import path from "node:path"; import { rimraf } from "rimraf"; import { beforeEach, describe } from "vitest"; -import { contrastResult, preContrastResult, startWithCommandPalette } from "./common/common-steps"; +import { + expectFilesInDir, + preContrastResult, + startWithCommandPalette, +} from "./common/common-steps"; import { inputProjectName, selectEmitters, selectTemplate } from "./common/create-steps"; import { mockShowOpenDialog } from "./common/mock-dialogs"; import { CaseScreenshot, tempDir, test } from "./common/utils"; @@ -81,7 +85,7 @@ describe.each(CreateCasesConfigList)("CreateTypespecProject", async (item) => { cs, app, ); - await contrastResult(expectedResults, workspacePath, cs); + await expectFilesInDir(expectedResults, workspacePath); app.close(); }); }); diff --git a/packages/typespec-vscode/test/extension/emit-typespec.test.ts b/packages/typespec-vscode/test/extension/emit-typespec.test.ts index 457436d219f..5f4e9ad2ae4 100644 --- a/packages/typespec-vscode/test/extension/emit-typespec.test.ts +++ b/packages/typespec-vscode/test/extension/emit-typespec.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { beforeAll, beforeEach, describe } from "vitest"; import { - contrastResult, + expectFilesInDir, packagesInstall, packPackages, preContrastResult, @@ -103,7 +103,7 @@ describe.each(EmitCasesConfigList)("EmitTypespecProject", async (item) => { restoreTspConfigFile(workspacePath, content); } const resultFilePath = path.resolve(workspacePath, "./tsp-output/@typespec"); - await contrastResult(expectedResults, resultFilePath, cs); + await expectFilesInDir(expectedResults, resultFilePath); app.close(); }); }); diff --git a/packages/typespec-vscode/test/extension/import-typespec.test.ts b/packages/typespec-vscode/test/extension/import-typespec.test.ts index e1c6a241f66..3e4ea9a186d 100644 --- a/packages/typespec-vscode/test/extension/import-typespec.test.ts +++ b/packages/typespec-vscode/test/extension/import-typespec.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { beforeAll, beforeEach, describe } from "vitest"; import { - contrastResult, + expectFilesInDir, packagesInstall, packPackages, preContrastResult, @@ -98,7 +98,7 @@ describe.each(ImportCasesConfigList)("ImportTypespecFromOpenApi3", async (item) app, ); const resultFilePath = path.resolve(workspacePath, "./ImportTypespecProjectEmptyFolder"); - await contrastResult(expectedResults, resultFilePath, cs); + await expectFilesInDir(expectedResults, resultFilePath); app.close(); }); }); From b1c009bf8d0c31548208338f6ffe80c5d96ca081 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 21 Jan 2026 14:09:52 -0500 Subject: [PATCH 3/5] fixes --- packages/typespec-vscode/test/extension/common/common-steps.ts | 2 +- packages/typespec-vscode/tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typespec-vscode/test/extension/common/common-steps.ts b/packages/typespec-vscode/test/extension/common/common-steps.ts index 58139ee2b92..a851e4125bf 100644 --- a/packages/typespec-vscode/test/extension/common/common-steps.ts +++ b/packages/typespec-vscode/test/extension/common/common-steps.ts @@ -27,7 +27,7 @@ export async function preContrastResult( } catch (e) { await cs.screenshot(page, "error"); app.close(); - throw new Error(errorMessage); + throw new Error(`${errorMessage} - Timed out waiting for text: "${text}" - ${e}`); } } diff --git a/packages/typespec-vscode/tsconfig.json b/packages/typespec-vscode/tsconfig.json index 4ed563a5456..71612145ff4 100644 --- a/packages/typespec-vscode/tsconfig.json +++ b/packages/typespec-vscode/tsconfig.json @@ -14,5 +14,5 @@ "types": ["mocha"], "rootDir": "." }, - "exclude": ["dist", "templates", "vitest.config.ts"] + "exclude": ["dist", "temp", "templates", "vitest.config.ts"] } From e32ee6b6643d13d49e7101cbd95fecef854b9a96 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 21 Jan 2026 15:46:04 -0500 Subject: [PATCH 4/5] revert --- .../changes/reneable-vscode-e2e-2026-0-21-17-54-15.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/reneable-vscode-e2e-2026-0-21-17-54-15.md diff --git a/.chronus/changes/reneable-vscode-e2e-2026-0-21-17-54-15.md b/.chronus/changes/reneable-vscode-e2e-2026-0-21-17-54-15.md new file mode 100644 index 00000000000..1c86f89982f --- /dev/null +++ b/.chronus/changes/reneable-vscode-e2e-2026-0-21-17-54-15.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: internal +packages: + - typespec-vscode +--- + +Reenable and improve vscode e2e tests From 3ccc770250113bdd85f575e2dbfbe13f44b61ffa Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 21 Jan 2026 15:46:53 -0500 Subject: [PATCH 5/5] > --- .../test/extension/common/mock-dialogs.ts | 98 ++++++++++++++++--- .../test/extension/import-typespec.test.ts | 13 +-- 2 files changed, 93 insertions(+), 18 deletions(-) diff --git a/packages/typespec-vscode/test/extension/common/mock-dialogs.ts b/packages/typespec-vscode/test/extension/common/mock-dialogs.ts index e8fd008ea2c..9c18bd73b12 100644 --- a/packages/typespec-vscode/test/extension/common/mock-dialogs.ts +++ b/packages/typespec-vscode/test/extension/common/mock-dialogs.ts @@ -7,6 +7,26 @@ export interface Dialog { }>; } +/** Special marker to indicate a sequence of results where each call returns the next item */ +const SEQUENCE_MARKER = "__sequence__" as const; + +export interface DialogResultSequence { + [SEQUENCE_MARKER]: true; + results: T[]; +} + +/** Create a sequence of results where each call returns the next item in the list */ +export function sequence(...results: T[]): DialogResultSequence { + return { + [SEQUENCE_MARKER]: true, + results, + }; +} + +function isSequence(value: unknown): value is DialogResultSequence { + return typeof value === "object" && value !== null && SEQUENCE_MARKER in value; +} + export type DialogMethodStub = { method: T; value: Awaited>; @@ -14,7 +34,14 @@ export type DialogMethodStub = { export type DialogMethodStubPartial = { method: T; - value: Partial>>; + value: + | Partial>> + | DialogResultSequence>>>; +}; + +type DialogMethodStubResolved = { + method: T; + value: Awaited> | DialogResultSequence>>; }; type DialogDefaults = { @@ -43,30 +70,67 @@ export function stubMultipleDialogs( ) { const mocksRequired = mocks.map((mock) => { const methodDefault = dialogDefaults[mock.method]; - if (!methodDefault) return mock as DialogMethodStub; + if (!methodDefault) return mock as DialogMethodStubResolved; + + if (isSequence(mock.value)) { + // Apply defaults to each result in the sequence + const resolvedResults = mock.value.results.map((result) => { + if (typeof result === "object") { + return { ...methodDefault, ...result }; + } + return result ?? methodDefault; + }); + return { + method: mock.method, + value: { __sequence__: true, results: resolvedResults }, + } as DialogMethodStubResolved; + } + if (typeof mock.value === "object") { mock.value = { ...methodDefault, ...mock.value }; } else { mock.value = mock.value ?? methodDefault; } - return mock as DialogMethodStub; + return mock as DialogMethodStubResolved; }); // https://github.com/microsoft/playwright/issues/8278#issuecomment-1009957411 - return app.evaluate(({ dialog }, mocks) => { + return app.evaluate(({ dialog, ...args }, mocks) => { mocks.forEach((mock) => { const thisDialog = dialog[mock.method]; if (!thisDialog) { throw new Error(`can't find ${mock.method} on dialog module.`); } - if (mock.method.endsWith("Sync")) { - dialog[mock.method] = () => { - return mock.value; - }; + + const isSeq = (v: unknown): v is { __sequence__: true; results: unknown[] } => + typeof v === "object" && v !== null && "__sequence__" in v; + + if (isSeq(mock.value)) { + let callIndex = 0; + const results = mock.value.results; + if (mock.method.endsWith("Sync")) { + dialog[mock.method] = () => { + const result = results[Math.min(callIndex, results.length - 1)]; + callIndex++; + + return result; + }; + } else { + dialog[mock.method] = async () => { + const result = results[Math.min(callIndex, results.length - 1)]; + return result; + }; + } } else { - dialog[mock.method] = async () => { - return mock.value; - }; + if (mock.method.endsWith("Sync")) { + dialog[mock.method] = () => { + return mock.value; + }; + } else { + dialog[mock.method] = async () => { + return mock.value; + }; + } } }); }, mocksRequired); @@ -74,8 +138,18 @@ export function stubMultipleDialogs( /** * Mock the `showOpenDialog`. Call this before something that will trigger the dialog. + * @param filePaths - Either a single array of file paths, or a sequence of arrays for multiple calls */ -export function mockShowOpenDialog(app: ElectronApplication, filePaths: string[]) { +export function mockShowOpenDialog( + app: ElectronApplication, + filePaths: string[] | DialogResultSequence, +) { + if (isSequence(filePaths)) { + const sequenceValue = sequence( + ...filePaths.results.map((r) => ({ filePaths: r, canceled: false })), + ); + return stubMultipleDialogs(app, [{ method: "showOpenDialog", value: sequenceValue }]); + } return stubDialog(app, "showOpenDialog", { filePaths, canceled: false, diff --git a/packages/typespec-vscode/test/extension/import-typespec.test.ts b/packages/typespec-vscode/test/extension/import-typespec.test.ts index 3e4ea9a186d..09a1f5dc3b9 100644 --- a/packages/typespec-vscode/test/extension/import-typespec.test.ts +++ b/packages/typespec-vscode/test/extension/import-typespec.test.ts @@ -6,9 +6,9 @@ import { packagesInstall, packPackages, preContrastResult, - startWithRightClick, + startWithCommandPalette, } from "./common/common-steps"; -import { mockShowOpenDialog } from "./common/mock-dialogs"; +import { mockShowOpenDialog, sequence } from "./common/mock-dialogs"; import { CaseScreenshot, tempDir, test, testfilesDir } from "./common/utils"; // Test files are copied into the temporary directory before tests run @@ -85,9 +85,10 @@ describe.each(ImportCasesConfigList)("ImportTypespecFromOpenApi3", async (item) const { page, app } = await launch({ workspacePath, }); + const targetPath = path.resolve(workspacePath, "ImportTypespecProjectEmptyFolder"); const openapifilepath = path.resolve(ImportTypespecProjectFolderPath, "openapi.3.0.yaml"); - await mockShowOpenDialog(app, [openapifilepath]); - await startWithRightClick(page, "Import TypeSpec from Openapi 3", cs); + await mockShowOpenDialog(app, sequence([targetPath], [openapifilepath])); + await startWithCommandPalette(page, "Import TypeSpec from Openapi 3", cs); await preContrastResult( page, @@ -97,8 +98,8 @@ describe.each(ImportCasesConfigList)("ImportTypespecFromOpenApi3", async (item) cs, app, ); - const resultFilePath = path.resolve(workspacePath, "./ImportTypespecProjectEmptyFolder"); - await expectFilesInDir(expectedResults, resultFilePath); + + await expectFilesInDir(expectedResults, targetPath); app.close(); }); });