Skip to content

feat: module separation of worker module #60

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

Merged
merged 9 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions infra/compiler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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))
}
}
9 changes: 9 additions & 0 deletions packages/vscode-wdio-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
5 changes: 4 additions & 1 deletion packages/vscode-wdio-worker/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode-wdio-worker/src/parsers/cucumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
17 changes: 13 additions & 4 deletions packages/vscode-wdio-worker/src/parsers/index.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand All @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions packages/vscode-wdio-worker/src/parsers/utils.ts
Original file line number Diff line number Diff line change
@@ -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<CucumberParser> {
return (await dynamicLoader(
context,
cucumberParser,
CUCUMBER_PARSER_PATH,
'parseCucumberFeature'
)) as CucumberParser
}

export async function getAstParser(context: WorkerMetaContext): Promise<AstParser> {
return (await dynamicLoader(context, astParser, AST_PARSER_PATH, 'parseTestCases')) as AstParser
}
5 changes: 3 additions & 2 deletions packages/vscode-wdio-worker/src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
}
Expand Down
39 changes: 39 additions & 0 deletions packages/vscode-wdio-worker/src/utils.ts
Original file line number Diff line number Diff line change
@@ -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<TempConfigFileCreator> {
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<unknown> {
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
}
4 changes: 2 additions & 2 deletions packages/vscode-wdio-worker/tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
4 changes: 3 additions & 1 deletion packages/vscode-wdio-worker/tests/parsers/cucumber.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
48 changes: 22 additions & 26 deletions packages/vscode-wdio-worker/tests/parsers/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -59,6 +52,7 @@ describe('Parser Index', () => {
],
},
]
const mockAstParser = vi.fn(() => jsMockTestData)

const cucumberMockTestData: CucumberTestData[] = [
{
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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')}`)
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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')
)
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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()
})
})
})
Loading