diff --git a/infra/compiler/src/index.ts b/infra/compiler/src/index.ts index 661f99d..2097fdc 100644 --- a/infra/compiler/src/index.ts +++ b/infra/compiler/src/index.ts @@ -60,6 +60,8 @@ if (entryPoints.length < 1) { throw new Error(`No export module found to build at: ${absWorkingDir}`) } +const outdir = path.resolve(absWorkingDir, 'dist') + const ctx = await context({ sourceRoot: absWorkingDir, entryPoints, @@ -69,10 +71,11 @@ const ctx = await context({ sourcemap: !options.production, sourcesContent: false, platform: 'node', - outdir: path.resolve(absWorkingDir, 'dist'), + outdir, outExtension: { '.js': '.cjs' }, external: ['vscode'], logLevel: 'silent', + metafile: true, plugins: [ /* add to the end of plugins array */ esbuildProblemMatcherPlugin, @@ -81,6 +84,10 @@ const ctx = await context({ if (options.watch) { await ctx.watch() } else { - await ctx.rebuild() + const result = await ctx.rebuild() await ctx.dispose() + + if (!options.production) { + fss.writeFileSync(path.join(outdir, 'meta.json'), JSON.stringify(result.metafile)) + } } diff --git a/packages/vscode-wdio-worker/package.json b/packages/vscode-wdio-worker/package.json index 47b6c5e..192eb30 100644 --- a/packages/vscode-wdio-worker/package.json +++ b/packages/vscode-wdio-worker/package.json @@ -6,6 +6,15 @@ "exports": { ".": { "import": "./dist/index.js" + }, + "./windows": { + "import": "./dist/config.js" + }, + "./parser/ast": { + "import": "./dist/parsers/js.js" + }, + "./parser/cucumber": { + "import": "./dist/parsers/cucumber.js" } }, "scripts": { diff --git a/packages/vscode-wdio-worker/src/config.ts b/packages/vscode-wdio-worker/src/config.ts index f5ade77..8d099dd 100644 --- a/packages/vscode-wdio-worker/src/config.ts +++ b/packages/vscode-wdio-worker/src/config.ts @@ -7,7 +7,10 @@ import * as t from '@babel/types' import recast from 'recast' const reporterIdentifierName = 'VscodeJsonReporter' -const VSCODE_REPORTER_PATH = path.resolve(__dirname, 'reporter.cjs') + +// This file is bundle as parser/ats.js at the package of vscode-webdriverio +// So, the correct reporter path is parent directory +const VSCODE_REPORTER_PATH = path.resolve(__dirname, '../reporter.cjs') /** * Since Windows cannot import by reporter file path due to issues with * the `initializePlugin` method of wdio-utils, the policy is to create a temporary configuration file. diff --git a/packages/vscode-wdio-worker/src/parsers/cucumber.ts b/packages/vscode-wdio-worker/src/parsers/cucumber.ts index 30df3e3..f34280d 100644 --- a/packages/vscode-wdio-worker/src/parsers/cucumber.ts +++ b/packages/vscode-wdio-worker/src/parsers/cucumber.ts @@ -12,7 +12,7 @@ import type { CucumberTestData, StepType, WorkerMetaContext } from '@vscode-wdio * @returns Array of test case information */ export function parseCucumberFeature(this: WorkerMetaContext, fileContent: string, uri: string) { - this.log.debug('Cucumber parser is used.') + this.log.debug(`Start parsing the cucumber feature file: ${uri}`) try { // Initialize the parser components const builder = new AstBuilder(IdGenerator.uuid()) diff --git a/packages/vscode-wdio-worker/src/parsers/index.ts b/packages/vscode-wdio-worker/src/parsers/index.ts index 2eb3efe..b39340f 100644 --- a/packages/vscode-wdio-worker/src/parsers/index.ts +++ b/packages/vscode-wdio-worker/src/parsers/index.ts @@ -1,11 +1,20 @@ import * as fs from 'node:fs/promises' import path from 'node:path' -import { parseCucumberFeature } from './cucumber.js' -import { parseTestCases } from './js.js' +import { getAstParser, getCucumberParser } from './utils.js' import type { ReadSpecsOptions } from '@vscode-wdio/types/api' import type { WorkerMetaContext } from '@vscode-wdio/types/worker' +async function parseFeatureFile(context: WorkerMetaContext, contents: string, normalizeSpecPath: string) { + const p = await getCucumberParser(context) + return p.call(context, contents, normalizeSpecPath) +} + +async function parseJsFile(context: WorkerMetaContext, contents: string, normalizeSpecPath: string) { + const p = await getAstParser(context) + return p.call(context, contents, normalizeSpecPath) +} + export async function parse(this: WorkerMetaContext, options: ReadSpecsOptions) { return await Promise.all( options.specs.map(async (spec) => { @@ -14,8 +23,8 @@ export async function parse(this: WorkerMetaContext, options: ReadSpecsOptions) const contents = await fs.readFile(normalizeSpecPath, { encoding: 'utf8' }) try { const testCases = isCucumberFeatureFile(normalizeSpecPath) - ? parseCucumberFeature.call(this, contents, normalizeSpecPath) // Parse Cucumber feature file - : parseTestCases.call(this, contents, normalizeSpecPath) // Parse JavaScript/TypeScript test file + ? await parseFeatureFile(this, contents, normalizeSpecPath) // Parse Cucumber feature file + : await parseJsFile(this, contents, normalizeSpecPath) // Parse JavaScript/TypeScript test file this.log.debug(`Successfully parsed: ${normalizeSpecPath}`) return { diff --git a/packages/vscode-wdio-worker/src/parsers/utils.ts b/packages/vscode-wdio-worker/src/parsers/utils.ts new file mode 100644 index 0000000..e27bbaf --- /dev/null +++ b/packages/vscode-wdio-worker/src/parsers/utils.ts @@ -0,0 +1,28 @@ +import path from 'node:path' + +import { dynamicLoader } from '../utils.js' +import type { WorkerMetaContext } from '@vscode-wdio/types/worker' +import type { parseCucumberFeature } from './cucumber.js' +import type { parseTestCases } from './js.js' + +export type CucumberParser = typeof parseCucumberFeature +export type AstParser = typeof parseTestCases + +const CUCUMBER_PARSER_PATH = path.resolve(__dirname, 'parser/cucumber.cjs') +const AST_PARSER_PATH = path.resolve(__dirname, 'parser/ast.cjs') + +let cucumberParser: CucumberParser | undefined +let astParser: AstParser | undefined + +export async function getCucumberParser(context: WorkerMetaContext): Promise { + return (await dynamicLoader( + context, + cucumberParser, + CUCUMBER_PARSER_PATH, + 'parseCucumberFeature' + )) as CucumberParser +} + +export async function getAstParser(context: WorkerMetaContext): Promise { + return (await dynamicLoader(context, astParser, AST_PARSER_PATH, 'parseTestCases')) as AstParser +} diff --git a/packages/vscode-wdio-worker/src/test.ts b/packages/vscode-wdio-worker/src/test.ts index 824d832..f71b055 100644 --- a/packages/vscode-wdio-worker/src/test.ts +++ b/packages/vscode-wdio-worker/src/test.ts @@ -3,7 +3,7 @@ import * as os from 'node:os' import { dirname, isAbsolute, join, resolve } from 'node:path' import { getLauncherInstance } from './cli.js' -import { createTempConfigFile, isWindows } from './config.js' +import { getTempConfigCreator, isWindows } from './utils.js' import type { RunTestOptions, TestResultData } from '@vscode-wdio/types/api' import type { ResultSet } from '@vscode-wdio/types/reporter' import type { LoggerInterface } from '@vscode-wdio/types/utils' @@ -43,7 +43,8 @@ export async function runTest(this: WorkerMetaContext, options: RunTestOptions): } if (isWindows()) { - configFile = await createTempConfigFile(options.configPath, outputDir.json!) + const creator = await getTempConfigCreator(this) + configFile = await creator(options.configPath, outputDir.json!) options.configPath = configFile wdioArgs.configPath = configFile } diff --git a/packages/vscode-wdio-worker/src/utils.ts b/packages/vscode-wdio-worker/src/utils.ts new file mode 100644 index 0000000..b447e37 --- /dev/null +++ b/packages/vscode-wdio-worker/src/utils.ts @@ -0,0 +1,39 @@ +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +import type { WorkerMetaContext } from '@vscode-wdio/types' +import type { createTempConfigFile } from './config.js' + +export type TempConfigFileCreator = typeof createTempConfigFile + +const VSCODE_WINDOWS_CONFIG_CREATOR_PATH = path.resolve(__dirname, 'parser/ast.cjs') + +let tempConfigCreator: TempConfigFileCreator | undefined + +export async function getTempConfigCreator(context: WorkerMetaContext): Promise { + return (await dynamicLoader( + context, + tempConfigCreator, + VSCODE_WINDOWS_CONFIG_CREATOR_PATH, + 'createTempConfigFile' + )) as TempConfigFileCreator +} + +export function isWindows() { + return process.platform === 'win32' +} + +export async function dynamicLoader( + context: WorkerMetaContext, + libModule: unknown, + modulePath: string, + fnName: string +): Promise { + if (libModule) { + context.log.debug(`Use cached ${path.dirname(modulePath)}`) + return libModule + } + context.log.debug(`Import ${path.basename(modulePath)}`) + libModule = (await import(pathToFileURL(modulePath).href))[fnName] + return libModule +} diff --git a/packages/vscode-wdio-worker/tests/config.test.ts b/packages/vscode-wdio-worker/tests/config.test.ts index cf2ac0e..4e52b73 100644 --- a/packages/vscode-wdio-worker/tests/config.test.ts +++ b/packages/vscode-wdio-worker/tests/config.test.ts @@ -26,13 +26,13 @@ vi.mock('node:path', async (importOriginal) => { return { default: { ...actual, - resolve: vi.fn((_, f) => `/path/to/${f}`), + resolve: vi.fn((_, f) => path.posix.resolve(`/path/to/parser/${f}`)), }, } }) describe('config.ts', () => { - const VSCODE_REPORTER_PATH = path.resolve(__dirname, 'reporter.cjs') + const VSCODE_REPORTER_PATH = path.resolve(__dirname, '../reporter.cjs') const reporterPathUrl = pathToFileURL(VSCODE_REPORTER_PATH).href beforeEach(() => { diff --git a/packages/vscode-wdio-worker/tests/parsers/cucumber.test.ts b/packages/vscode-wdio-worker/tests/parsers/cucumber.test.ts index 9da49a0..c47758b 100644 --- a/packages/vscode-wdio-worker/tests/parsers/cucumber.test.ts +++ b/packages/vscode-wdio-worker/tests/parsers/cucumber.test.ts @@ -340,7 +340,9 @@ describe('Cucumber Parser', () => { parseCucumberFeature.call(mockContext, basicFeatureContent, 'basic-feature.feature') // Verify debug log was called - expect(mockContext.log.debug).toHaveBeenCalledWith('Cucumber parser is used.') + expect(mockContext.log.debug).toHaveBeenCalledWith( + 'Start parsing the cucumber feature file: basic-feature.feature' + ) }) it('should include proper source ranges for each element', () => { diff --git a/packages/vscode-wdio-worker/tests/parsers/index.test.ts b/packages/vscode-wdio-worker/tests/parsers/index.test.ts index 4f0d2cb..2a11eb6 100644 --- a/packages/vscode-wdio-worker/tests/parsers/index.test.ts +++ b/packages/vscode-wdio-worker/tests/parsers/index.test.ts @@ -5,29 +5,22 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' // Import parse function from parsers -import { parseCucumberFeature } from '../../src/parsers/cucumber.js' import { parse } from '../../src/parsers/index.js' -import { parseTestCases } from '../../src/parsers/js.js' +import { getAstParser, getCucumberParser } from '../../src/parsers/utils.js' import type { ReadSpecsOptions } from '@vscode-wdio/types/api' import type { WorkerMetaContext, TestData, CucumberTestData } from '@vscode-wdio/types/worker' -// Import the types // Mock fs module vi.mock('node:fs/promises', () => ({ readFile: vi.fn(), })) // Mock the parsers -vi.mock('../../src/parsers/cucumber.js', () => ({ - parseCucumberFeature: vi.fn(), +vi.mock('../../src/parsers/utils.js', () => ({ + getCucumberParser: vi.fn(), + getAstParser: vi.fn(), })) -vi.mock('../../src/parsers/js.js', () => ({ - parseTestCases: vi.fn(), -})) - -// Import mocked modules - describe('Parser Index', () => { // Create mock context const mockContext: WorkerMetaContext = { @@ -59,6 +52,7 @@ describe('Parser Index', () => { ], }, ] + const mockAstParser = vi.fn(() => jsMockTestData) const cucumberMockTestData: CucumberTestData[] = [ { @@ -87,13 +81,15 @@ describe('Parser Index', () => { metadata: {}, }, ] + const mockCucumberParser = vi.fn(() => cucumberMockTestData) beforeEach(() => { vi.resetAllMocks() // Mock the parseTestCases and parseCucumberFeature functions - vi.mocked(parseTestCases).mockReturnValue(jsMockTestData) - vi.mocked(parseCucumberFeature).mockReturnValue(cucumberMockTestData) + // vi.mocked(parseTestCases).mockReturnValue(jsMockTestData) + vi.mocked(getCucumberParser).mockResolvedValue(mockCucumberParser) + vi.mocked(getAstParser).mockResolvedValue(mockAstParser) }) afterEach(() => { @@ -119,8 +115,8 @@ describe('Parser Index', () => { expect(result[0].tests).toEqual(jsMockTestData) // Verify the correct parser was called - expect(parseTestCases).toHaveBeenCalledWith(jsTestContent, path.normalize('/path/to/test.js')) - expect(parseCucumberFeature).not.toHaveBeenCalled() + expect(mockAstParser).toHaveBeenCalledWith(jsTestContent, path.normalize('/path/to/test.js')) + expect(mockCucumberParser).not.toHaveBeenCalled() // Verify logging expect(mockContext.log.debug).toHaveBeenCalledWith(`Parse spec file: ${path.normalize('/path/to/test.js')}`) @@ -147,8 +143,8 @@ describe('Parser Index', () => { expect(result[0].tests).toEqual(jsMockTestData) // Verify the correct parser was called - expect(parseTestCases).toHaveBeenCalledWith(jsTestContent, path.normalize('/path/to/test.ts')) - expect(parseCucumberFeature).not.toHaveBeenCalled() + expect(mockAstParser).toHaveBeenCalledWith(jsTestContent, path.normalize('/path/to/test.ts')) + expect(mockCucumberParser).not.toHaveBeenCalled() }) it('should parse Cucumber feature files correctly', async () => { @@ -169,11 +165,11 @@ describe('Parser Index', () => { expect(result[0].tests).toEqual(cucumberMockTestData) // Verify the correct parser was called - expect(parseCucumberFeature).toHaveBeenCalledWith( + expect(mockCucumberParser).toHaveBeenCalledWith( cucumberFeatureContent, path.normalize('/path/to/test.feature') ) - expect(parseTestCases).not.toHaveBeenCalled() + expect(mockAstParser).not.toHaveBeenCalled() }) it('should handle uppercase feature file extensions', async () => { @@ -194,11 +190,11 @@ describe('Parser Index', () => { expect(result[0].tests).toEqual(cucumberMockTestData) // Verify the correct parser was called - expect(parseCucumberFeature).toHaveBeenCalledWith( + expect(mockCucumberParser).toHaveBeenCalledWith( cucumberFeatureContent, path.normalize('/path/to/test.FEATURE') ) - expect(parseTestCases).not.toHaveBeenCalled() + expect(mockAstParser).not.toHaveBeenCalled() }) it('should parse multiple spec files', async () => { @@ -231,8 +227,8 @@ describe('Parser Index', () => { expect(result[1].tests).toEqual(cucumberMockTestData) // Verify the correct parsers were called - expect(parseTestCases).toHaveBeenCalledWith(jsTestContent, path.normalize('/path/to/test.js')) - expect(parseCucumberFeature).toHaveBeenCalledWith( + expect(mockAstParser).toHaveBeenCalledWith(jsTestContent, path.normalize('/path/to/test.js')) + expect(mockCucumberParser).toHaveBeenCalledWith( cucumberFeatureContent, path.normalize('/path/to/test.feature') ) @@ -268,7 +264,7 @@ describe('Parser Index', () => { expect(result[0].spec).toBe(path.normalize('C:\\path\\to\\test.js')) // Verify the parser was called with the normalized path - expect(parseTestCases).toHaveBeenCalledWith(jsTestContent, path.normalize('C:\\path\\to\\test.js')) + expect(mockAstParser).toHaveBeenCalledWith(jsTestContent, path.normalize('C:\\path\\to\\test.js')) }) it('should handle empty specs array', async () => { @@ -283,8 +279,8 @@ describe('Parser Index', () => { // Verify expect(result).toEqual([]) expect(fs.readFile).not.toHaveBeenCalled() - expect(parseTestCases).not.toHaveBeenCalled() - expect(parseCucumberFeature).not.toHaveBeenCalled() + expect(mockAstParser).not.toHaveBeenCalled() + expect(mockCucumberParser).not.toHaveBeenCalled() }) }) }) diff --git a/packages/vscode-wdio-worker/tests/test.test.ts b/packages/vscode-wdio-worker/tests/test.test.ts index 0b1d098..6a2f2bd 100644 --- a/packages/vscode-wdio-worker/tests/test.test.ts +++ b/packages/vscode-wdio-worker/tests/test.test.ts @@ -5,8 +5,8 @@ import { join } from 'node:path' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { getLauncherInstance } from '../src/cli.js' -import { createTempConfigFile, isWindows } from '../src/config.js' import { runTest } from '../src/test.js' +import { getTempConfigCreator, isWindows } from '../src/utils.js' import type { Dirent } from 'node:fs' import type { WorkerMetaContext } from '@vscode-wdio/types/worker' @@ -21,10 +21,11 @@ vi.mock('../src/cli.js', () => { ) return { getLauncherInstance } }) -vi.mock('../src/config.js', () => { + +vi.mock('../src/utils.js', () => { return { - createTempConfigFile: vi.fn(async (config) => config), isWindows: vi.fn(() => false), + getTempConfigCreator: vi.fn(), } }) @@ -52,6 +53,7 @@ describe('runTest', () => { const mockResultDir = '/mock/tmp/dir/result-xyz123' const mockResultFile = 'wdio-0-0.json' const mockResultData = JSON.stringify({ some: 'test result' }) + const mockTempConfigCreator = vi.fn(async () => '/path/to/customized/wdio.conf.ts') beforeEach(() => { // Reset mocks @@ -63,10 +65,13 @@ describe('runTest', () => { vi.mocked(fs.mkdir).mockResolvedValue(undefined) vi.mocked(fs.mkdtemp).mockResolvedValue(mockResultDir) vi.mocked(fs.access).mockResolvedValue(undefined) + // @ts-ignore vi.mocked(fs.readdir).mockResolvedValue([mockResultFile as unknown as Dirent]) vi.mocked(fs.readFile).mockResolvedValue(Buffer.from(mockResultData)) vi.mocked(fs.rm).mockResolvedValue(undefined) + vi.mocked(getTempConfigCreator).mockResolvedValue(mockTempConfigCreator) + // Mock console methods vi.spyOn(console, 'log').mockImplementation(() => {}) vi.spyOn(console, 'error').mockImplementation(() => {}) @@ -104,7 +109,6 @@ describe('runTest', () => { it('should run tests successfully and return results on the windows', async () => { vi.mocked(isWindows).mockReturnValue(true) - vi.mocked(createTempConfigFile).mockResolvedValue('/path/to/customized/wdio.conf.ts') // Act const result = await runTest.call(mockContext, mockOptions) @@ -122,7 +126,7 @@ describe('runTest', () => { expect(fs.mkdtemp).toHaveBeenCalledWith(join(mockTmpDir, 'vscode-webdriverio', 'result-')) // Verify createTempConfigFile were called - expect(createTempConfigFile).toHaveBeenCalledWith(mockConfigFile, mockResultDir) + expect(mockTempConfigCreator).toHaveBeenCalledWith(mockConfigFile, mockResultDir) // Verify result files were read expect(fs.readdir).toHaveBeenCalledWith(mockResultDir) diff --git a/packages/vscode-webdriverio/package.json b/packages/vscode-webdriverio/package.json index 11daea3..36ec3d6 100644 --- a/packages/vscode-webdriverio/package.json +++ b/packages/vscode-webdriverio/package.json @@ -19,6 +19,14 @@ "./reporter": { "requireSource": "./src/reporter.ts", "require": "./dist/reporter.cjs" + }, + "./parser/ast": { + "requireSource": "./src/parser/ast.ts", + "require": "./dist/parser/ast.cjs" + }, + "./parser/cucumber": { + "requireSource": "./src/parser/cucumber.ts", + "require": "./dist/parser/cucumber.cjs" } }, "scripts": { diff --git a/packages/vscode-webdriverio/src/parser/ast.ts b/packages/vscode-webdriverio/src/parser/ast.ts new file mode 100644 index 0000000..dd3275d --- /dev/null +++ b/packages/vscode-webdriverio/src/parser/ast.ts @@ -0,0 +1,2 @@ +export { createTempConfigFile } from '@vscode-wdio/worker/windows' +export { parseTestCases } from '@vscode-wdio/worker/parser/ast' diff --git a/packages/vscode-webdriverio/src/parser/cucumber.ts b/packages/vscode-webdriverio/src/parser/cucumber.ts new file mode 100644 index 0000000..7e3711d --- /dev/null +++ b/packages/vscode-webdriverio/src/parser/cucumber.ts @@ -0,0 +1 @@ +export { parseCucumberFeature } from '@vscode-wdio/worker/parser/cucumber'