import url from 'node:url' import path from 'node:path' import { vi, describe, it, expect, } from 'vitest' import ConfigParser from '../../src/node/ConfigParser.js' import type { MockFileContent } from '../lib/MockFileContentBuilder.js' import MockFileContentBuilder from '../lib/MockFileContentBuilder.js' import type { FilePathsAndContents, MockSystemFilePath, MockSystemFolderPath } from '../lib/MockPathService.js' import ConfigParserBuilder from '../lib/ConfigParserBuilder.js' import { FileNamed, realReadFilePair, realRequiredFilePair } from '../lib/FileNamed.js' const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) const root = path.resolve(__dirname, '..', '..') const FIXTURES_PATH = path.resolve(__dirname, '..', '__fixtures__') const FIXTURES_CONF = path.resolve(FIXTURES_PATH, 'wdio.conf.ts') const FIXTURES_CONF_RDC = path.resolve(FIXTURES_PATH, 'wdio.conf.rdc.ts') const FIXTURES_CONF_ARRAY = path.resolve(FIXTURES_PATH, 'wdio.array.conf.ts') const FIXTURES_LOCAL_CONF = path.resolve(FIXTURES_PATH, 'wdio.local.conf.ts') const FIXTURES_PREFIX_CONF = path.resolve(FIXTURES_PATH, 'wdio.wdio-prefix.conf.ts') const FIXTURES_DEFAULT_CONF = path.resolve(FIXTURES_PATH, 'wdio.default.conf.ts') const FIXTURES_CUCUMBER_FEATURE_A_LINE_2 = path.resolve(FIXTURES_PATH, 'test-a.feature:2') const FIXTURES_CUCUMBER_FEATURE_A_LINE_2_AND_12 = path.resolve(FIXTURES_PATH, 'test-a.feature:2:12') const FIXTURES_CUCUMBER_FEATURE_B_LINE_7 = path.resolve(FIXTURES_PATH, 'test-b.feature:7') const INDEX_PATH = path.resolve(__dirname, '..', '..', 'src', 'index.ts') vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger'))) class CustomServiceOrReporter {} /** * Entirely in memory config structure to avoid reading the file system at all * * Ideally we wouldn't need a fixtures folder, or it would have very minimal files in it and changes * needed for tests would be modified on top of it. * * This is provided as a step to that direction. * * @constructor */ // eslint-disable-next-line camelcase const TestWdioConfig_AllInMemory = (): MockFileContent => ({ config: { user: 'foobar', key: '50fa142c-3121-4gb0-9p07-8q326vvbq7b0', specs: [path.join(root, '/tests/*.test.ts')], exclude: [ path.join(root, '/tests/validateConfig.test.ts') ], capabilities: [{ browserName: 'chrome' }], suites: { unit: [path.join(root, '/tests/configparser.test.ts')], mobile: [path.join(root, '/tests/node/FileSystemPathService.test.ts')], functional: [ path.join(root, '/tests/validateConfig.test.ts'), path.join(root, '/tests/..', 'src/index.ts') ] }, services: ['appium', [CustomServiceOrReporter, { foo: 'bar' }]], reporters: ['spec', [CustomServiceOrReporter, { foo: 'bar' }]], } }) /** * Represent everything in the fixtures directory (at time of writing) * * All ts files are read and configs are loaded in from disk, so all * parsing facilities should work as they would with the real file system. * * @constructor */ // eslint-disable-next-line camelcase async function MockedFileSystem_LoadingAsMuchAsCanFromFileSystem(): Promise { return [ realReadFilePair(path.resolve(FIXTURES_PATH, '../validateConfig.test.ts')), realReadFilePair(path.resolve(FIXTURES_PATH, '../node/configparser.test.ts')), realReadFilePair(path.resolve(FIXTURES_PATH, '../utils.test.ts')), realReadFilePair(path.resolve(FIXTURES_PATH, '../node/FileSystemPathService.test.ts')), FileNamed(path.resolve(FIXTURES_PATH, 'test.cjs')).withContents('test file contents'), FileNamed(path.resolve(FIXTURES_PATH, 'test.es6')).withContents('test file contents'), FileNamed(path.resolve(FIXTURES_PATH, 'test.java')).withContents('test file contents'), FileNamed(path.resolve(FIXTURES_PATH, 'test.mjs')).withContents('test file contents'), FileNamed(path.resolve(FIXTURES_PATH, 'test-a.feature')).withContents('feature file contents'), FileNamed(path.resolve(FIXTURES_PATH, 'test-b.feature')).withContents('feature file contents'), FileNamed(path.resolve(FIXTURES_PATH, 'typescript.ts')).withContents('test file contents'), await realRequiredFilePair(path.resolve(FIXTURES_PATH, 'wdio.array.conf.ts')), await realRequiredFilePair(path.resolve(FIXTURES_PATH, 'wdio.conf.multiremote.rdc.ts')), await realRequiredFilePair(path.resolve(FIXTURES_PATH, 'wdio.conf.rdc.ts')), await realRequiredFilePair(path.resolve(FIXTURES_PATH, 'wdio.conf.ts')), await realRequiredFilePair(path.resolve(FIXTURES_PATH, 'wdio.local.conf.ts')), await realRequiredFilePair(path.resolve(INDEX_PATH)) ] } /** * Represent everything in the fixtures directory (at time of writing) * * Should be fast b/c only loads config, and should work for most machinery. * But if anything requires configs recursively loading other configs, you * probably want to use MockedFileSystem_LoadingAsMuchAsCanFromFileSystem instead. * @param configFilepath * @constructor */ // eslint-disable-next-line camelcase function MockedFileSystem_OnlyLoadingConfig(baseDir: MockSystemFolderPath, configFilepath: MockSystemFilePath): FilePathsAndContents { return [ FileNamed(configFilepath).withContents(TestWdioConfig_AllInMemory()), FileNamed(path.join(baseDir, '../validateConfig.test.ts')).withContents('test contents'), FileNamed(path.join(baseDir, '../node/configparser.test.ts')).withContents('test contents'), FileNamed(path.join(baseDir, '../utils.test.ts')).withContents('test contents'), FileNamed(path.join(baseDir, '../node/FileSystemPathService.test.ts')).withContents('test contents'), FileNamed(path.join(baseDir, 'test.cjs')).withContents('test contents'), FileNamed(path.join(baseDir, 'test.es6')).withContents('test contents'), FileNamed(path.join(baseDir, 'test.java')).withContents('test contents'), FileNamed(path.join(baseDir, 'test.mjs')).withContents('test contents'), FileNamed(path.join(baseDir, 'test-a.feature')).withContents('feature file contents'), FileNamed(path.join(baseDir, 'test-b.feature')).withContents('feature file contents'), FileNamed(path.join(baseDir, 'typescript.ts')).withContents('test contents'), FileNamed(path.join(baseDir, 'prefix-test-01.ts')).withContents('test contents'), FileNamed(path.join(baseDir, 'prefix-test-02.ts')).withContents('test contents'), FileNamed(path.join(baseDir, 'wdio.conf.multiremote.rdc.ts')).withContents('config contents'), FileNamed(path.join(baseDir, 'wdio.conf.rdc.ts')).withContents('config contents'), FileNamed(path.join(baseDir, 'wdio.conf.ts')).withContents('config contents'), FileNamed(path.join(baseDir, 'wdio.local.conf.ts')).withContents('config contents'), FileNamed(path.join(baseDir, 'wdio.wdio-prefix.conf.ts')).withContents('config contents'), FileNamed(path.join(baseDir, '../app/src/index.ts')).withContents('source contents')] } function ConfigParserForTest(config = FIXTURES_CONF) { return ConfigParserBuilder.withBaseDir(FIXTURES_PATH, config) .withFiles( MockedFileSystem_OnlyLoadingConfig(FIXTURES_PATH, config) ).build() } async function ConfigParserForTestWithAllFiles(configPath: string, args = {}) { return ConfigParserBuilder.withBaseDir(FIXTURES_PATH, configPath, args) .withFiles( await MockedFileSystem_LoadingAsMuchAsCanFromFileSystem() ).build() } describe('ConfigParser', () => { it('should throw if getFilePaths is not a string', () => { expect(() => ConfigParser.getFilePaths(123 as any, '/foo/bar')).toThrow() }) it('enables coverage', async () => { const c1 = new ConfigParser('/wdio.conf.js', { coverage: true }) c1['addConfigFile'] = vi.fn() c1['merge'] = vi.fn() c1['_config'] = { runner: 'browser' } as any await c1.initialize({}) expect(c1['_config']).toMatchSnapshot() const c2 = new ConfigParser('/wdio.conf.js', { coverage: true }) c2['addConfigFile'] = vi.fn() c2['merge'] = vi.fn() c2['_config'] = { runner: ['browser', { coverage: { enabled: false, statements: 100 } }] } as any await c2.initialize({}) expect(c2['_config']).toMatchSnapshot() }) it('disables coverage', async () => { const c1 = new ConfigParser('/wdio.conf.js', { coverage: false }) c1['addConfigFile'] = vi.fn() c1['merge'] = vi.fn() c1['_config'] = { runner: 'browser' } as any await c1.initialize({}) expect(c1['_config']).toMatchSnapshot() const c2 = new ConfigParser('/wdio.conf.js', { coverage: false }) c2['addConfigFile'] = vi.fn() c2['merge'] = vi.fn() c2['_config'] = { runner: ['browser', { coverage: { enabled: true, statements: 100 } }] } as any await c2.initialize({}) expect(c2['_config']).toMatchSnapshot() }) describe('addConfigFile', () => { it('should throw if config file is not a string', async () => { const configParser = await ConfigParserForTest() await expect(() => configParser['addConfigFile'](123 as any)).rejects.toThrow() }) it('should throw if config file does not exist (absolute path)', async () => { const configParser = await ConfigParserForTest() expect(() => configParser['addConfigFile'](path.resolve(__dirname, 'foobar.conf.ts'))).rejects.toThrow() }) it('should throw if config file does not exist (relative path)', async () => { const configParser = await ConfigParserForTest() expect(() => configParser['addConfigFile']('foobar.conf.ts')).rejects.toThrow() }) it('should throw if config file is not a config file (absolute path)', async () => { const configParser = await ConfigParserForTest() expect(() => configParser['addConfigFile'](path.resolve(FIXTURES_PATH, 'test-a.feature'))).rejects.toThrow() }) it('should throw if config file is not a config file (relative path)', async () => { const configParser = await ConfigParserForTest() expect(() => configParser['addConfigFile']('test-a.feature')).rejects.toThrow() }) }) describe('merge', () => { const isWindows = process.platform === 'win32' it('should overwrite specs if piped into cli command', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ specs: [INDEX_PATH] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) expect(specs).toContain(INDEX_PATH) }) it('should allow specifying a spec file', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: [INDEX_PATH] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) expect(specs).toContain(INDEX_PATH) }) it('should allow specifying a spec file which is Cucumber feature file with line number', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ spec: [FIXTURES_CUCUMBER_FEATURE_A_LINE_2] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) let featureFileWithoutLine = '' featureFileWithoutLine = isWindows ? FIXTURES_CUCUMBER_FEATURE_A_LINE_2.split(':')[0] + ':' + FIXTURES_CUCUMBER_FEATURE_A_LINE_2.split(':')[1] : FIXTURES_CUCUMBER_FEATURE_A_LINE_2.split(':')[0] expect(specs).toContain(featureFileWithoutLine) }) it('should allow specifying a spec file which is Cucumber feature file with line numbers', async () => { const configParser = await ConfigParserForTest() await configParser['addConfigFile'](FIXTURES_CONF) configParser['merge']({ spec: [FIXTURES_CUCUMBER_FEATURE_A_LINE_2_AND_12] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) let featureFileWithoutLine = '' featureFileWithoutLine = isWindows ? FIXTURES_CUCUMBER_FEATURE_A_LINE_2_AND_12.split(':')[0] + ':' + FIXTURES_CUCUMBER_FEATURE_A_LINE_2_AND_12.split(':')[1] : FIXTURES_CUCUMBER_FEATURE_A_LINE_2_AND_12.split(':')[0] expect(specs).toContain(featureFileWithoutLine) }) it('should allow specifying a spec file which is Cucumber feature files with line number', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ spec: [FIXTURES_CUCUMBER_FEATURE_A_LINE_2, FIXTURES_CUCUMBER_FEATURE_B_LINE_7] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(2) let featureFileA = '' let featureFileB = '' if (isWindows) { featureFileA = FIXTURES_CUCUMBER_FEATURE_A_LINE_2.split(':')[0] + ':' + FIXTURES_CUCUMBER_FEATURE_A_LINE_2.split(':')[1] featureFileB = FIXTURES_CUCUMBER_FEATURE_B_LINE_7.split(':')[0] + ':' + FIXTURES_CUCUMBER_FEATURE_B_LINE_7.split(':')[1] } else { featureFileA = FIXTURES_CUCUMBER_FEATURE_A_LINE_2.split(':')[0] featureFileB = FIXTURES_CUCUMBER_FEATURE_B_LINE_7.split(':')[0] } expect(specs).toContain(featureFileA) expect(specs).toContain(featureFileB) }) it('should allow specifying mutliple single spec file', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: [INDEX_PATH, FIXTURES_CONF] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(2) expect(specs).toContain(INDEX_PATH) expect(specs).toContain(FIXTURES_CONF) }) it('should allow to specify partial matching spec file', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: ['PathService'] }) const specs = configParser.getSpecs() expect(specs).toContain(path.join(__dirname, 'FileSystemPathService.test.ts')) }) it('should handle an array in the config_specs', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF_ARRAY) await configParser.initialize({ spec: ['PathService'] }) const specs = configParser.getSpecs() expect(specs).toContain(path.join(__dirname, 'FileSystemPathService.test.ts')) }) it('should exclude duplicate spec files', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: [INDEX_PATH, INDEX_PATH] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) expect(specs).toContain(INDEX_PATH) }) it('should throw if specified spec file does not exist', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) const hasError = await configParser.initialize({ spec: [path.resolve(__dirname, 'foobar.ts')] }) .then(() => false, () => true) expect(hasError).toBe(true) }) it('should allow to specify multiple suites', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ suite: ['unit', 'functional', 'mobile'] }) const specs = configParser.getSpecs() expect(specs).toContain(__filename) expect(specs).toContain(INDEX_PATH) expect(specs).not.toContain(path.join(__dirname, 'validateConfig.test.ts')) expect(specs).toContain(path.join(__dirname, 'FileSystemPathService.test.ts')) }) it('should throw when suite is not defined', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ suite: ['blabla'] }) expect(() => configParser.getSpecs()).toThrow(/The suite\(s\) "blabla" you specified don't exist/) }) it('should throw when multiple suites are not defined', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ suite: ['blabla', 'lala'] }) expect(() => configParser.getSpecs()).toThrow(/The suite\(s\) "blabla", "lala" you specified don't exist/) }) it('should allow to specify a single suite', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF, { suite: ['mobile'] }) await configParser.initialize({ suite: ['mobile'] }) const specs = configParser.getSpecs() // @ts-ignore const suite = configParser.getConfig().suite expect(suite).toHaveLength(1) expect(specs).toHaveLength(1) expect(specs).toContain(path.join(__dirname, 'FileSystemPathService.test.ts')) }) it('should not overwrite host and port if specified in host file', async () => { const configParser = ConfigParserBuilder.withBaseDir(FIXTURES_PATH, FIXTURES_LOCAL_CONF) .withFiles( [ FileNamed(FIXTURES_LOCAL_CONF).withContents({ config: { hostname: '127.0.0.1', port: 4444, capabilities: [{ browserName: 'chrome' }] } }) ] ).build() await configParser.initialize({ user: 'barfoo', key: '50fa1411-3121-4gb0-9p07-8q326vvbq7b0' }) const config = configParser.getConfig() expect(config.hostname).toBe('127.0.0.1') expect(config.port).toBe(4444) }) it('should be able to read config file if object is attached to default', async () => { const configParser = ConfigParserBuilder.withBaseDir(FIXTURES_PATH, FIXTURES_DEFAULT_CONF).withFiles([ FileNamed(FIXTURES_DEFAULT_CONF).withContents({ default: { config: { foo: 'bar', capabilities: [{ browserName: 'chrome' }] } } }) ]).build() await configParser.initialize() const config = configParser.getConfig() expect(config['foo']).toBe('bar') }) it('should allow specifying a exclude file', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: [INDEX_PATH, FIXTURES_CONF], exclude: [INDEX_PATH] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) expect(specs).toContain(FIXTURES_CONF) }) it('should allow specifying multiple exclude files', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) configParser.initialize({ spec: [INDEX_PATH, FIXTURES_CONF], exclude: [INDEX_PATH, FIXTURES_CONF] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(0) }) it('should throw if specified exclude file does not exist', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) const hasError = await configParser.initialize({ exclude: [path.resolve(__dirname, 'foobar.ts')] }).then(() => false, () => true) expect(hasError).toBe(true) }) it('should allow specifying a glob pattern for exclude', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ spec: [INDEX_PATH, FIXTURES_CONF] }) // first validate that the conf fixture 'spec' was merged successfully expect(configParser.getSpecs()).toHaveLength(1) configParser['merge']({ exclude: [path.join(__dirname, '..', '**', '*conf*').replace(/\\/g, '/')] }) // then after merging an exclude containing a glob pattern, validate that the exclude // attribute contains multiple items and the filtering on the spec attribute works expect(configParser.getConfig().exclude.length).toBeGreaterThan(0) expect(configParser.getSpecs()).toHaveLength(0) }) it('should overwrite config exclude if piped into cli command', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize() // check that the initial exclude from the config filtered out 1 spec (out of a total of 4) expect(configParser.getSpecs()).toHaveLength(3) configParser['merge']({ exclude: [INDEX_PATH] }) // after overriding the config exclude with cli exclude check that the initial config exclude is discarded expect(configParser.getSpecs()).toHaveLength(4) }) it('should not overwrite config and capabilities exclude if piped into cli command', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize() expect(configParser.getSpecs()).toHaveLength(3) configParser['merge']({ exclude: [FIXTURES_CONF] }) const specs = configParser.getSpecs([FIXTURES_CONF, FIXTURES_CONF_RDC], [FIXTURES_CONF_RDC]) // FIXTURES_CONF is in the exclude so this expects empty expect(specs).toEqual([]) }) it('should not overwrite config and capabilities exclude if piped into cli command with suite', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) const requireLibPath = path.join(__dirname, 'FileSystemPathService.test.ts') const configParserPath = path.join(__dirname, 'configparser.test.ts') await configParser.initialize({ suite: ['mobile', 'unit'] }) // the initial list of specs has the ones defined in the suites passed via cli 'suite' (RequireLibrary & configparser) expect(configParser.getSpecs()).toHaveLength(2) // set a cli exclude configParser['merge']({ exclude: [requireLibPath] }) // set capability 'specs' and 'exclude' const specs = configParser.getSpecs([configParserPath, requireLibPath], [configParserPath]) expect(specs).toHaveLength(0) }) it('should overwrite specs w/ wdio:specs files from capabilitoes', async () => { const configParser = await ConfigParserForTest(FIXTURES_PREFIX_CONF) const prefixedTestFile = path.resolve(FIXTURES_PATH, 'prefix-test-01.ts') await configParser.initialize({ 'wdio:specs': [prefixedTestFile] }) const specs = configParser.getSpecs([prefixedTestFile]) expect(specs).toContain(prefixedTestFile) }) it('should overwrite exclude w/ wdio:exclude files from capabilities', async () => { const configParser = await ConfigParserForTest(FIXTURES_PREFIX_CONF) const prefixedTestFile = path.resolve(FIXTURES_PATH, 'prefix-test-01.ts') const excludedTestFile = path.resolve(FIXTURES_PATH, 'prefix-test-02.ts') await configParser.initialize({ 'wdio:specs': [prefixedTestFile], 'wdio:exclude' : [excludedTestFile] }) const specs = configParser.getSpecs([prefixedTestFile, excludedTestFile]) expect(specs).not.toContain(excludedTestFile) }) it('should set hooks to empty arrays as default', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({}) expect(configParser.getConfig().onPrepare).toHaveLength(0) expect(configParser.getConfig().before).toHaveLength(0) expect(configParser.getConfig().after).toHaveLength(0) expect(configParser.getConfig().onComplete).toHaveLength(0) }) it('should overwrite hooks if provided', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ onPrepare: vi.fn(), before: vi.fn(), after: vi.fn(), onComplete: vi.fn() }) expect(typeof configParser.getConfig().onPrepare).toBe('function') expect(typeof configParser.getConfig().before).toBe('function') expect(typeof configParser.getConfig().after).toBe('function') expect(typeof configParser.getConfig().onComplete).toBe('function') }) it('should overwrite capabilities', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize() expect(configParser.getCapabilities()).toMatchObject([{ browserName: 'chrome' }]) configParser['merge']({ capabilities: [{ browserName: 'safari' }], }) expect(configParser.getCapabilities()).toMatchObject([{ browserName: 'safari' }]) }) it('should ensure there we do not duplicate class entries', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize({ services: [ 'appium', 'sauce', [CustomServiceOrReporter, { foo: 'bar' }] ], reporters: [ 'dot', 'spec', [CustomServiceOrReporter, { foo: 'bar' }] as any ] }) const { services, reporters } = configParser.getConfig() expect(services).toHaveLength(3) expect(services).toMatchInlineSnapshot(` [ "appium", "sauce", [ [Function], { "foo": "bar", }, ], ] `) expect(reporters).toHaveLength(3) expect(reporters).toMatchInlineSnapshot(` [ "dot", "spec", [ [Function], { "foo": "bar", }, ], ] `) }) it('should ensure capabilities are not duped when utilizing launcher', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF, { capabilities: [ { platformName: 'chrome', }, ] }) await configParser.initialize({ capabilities: [ { platformName: 'chrome', }, ] }) const { capabilities } = configParser.getConfig() expect(capabilities).toHaveLength(1) }) }) describe('addService', () => { it('should only add functions', async () => { const configParser = new ConfigParser(FIXTURES_CONF) await configParser.initialize() configParser.addService({ onPrepare: vi.fn(), before: undefined, // @ts-ignore test invalid param beforeTest: 123, afterTest: () => 'foobar', // @ts-ignore test invalid param after: [1, () => 'barfoo', () => 'lala'], onComplete: vi.fn() }) expect(configParser['_config'].before).toHaveLength(0) expect(configParser['_config'].beforeTest).toHaveLength(0) expect(configParser['_config'].afterTest).toHaveLength(1) expect(configParser['_config'].after).toHaveLength(2) expect(configParser['_config'].onPrepare).toHaveLength(1) expect(configParser['_config'].onComplete).toHaveLength(1) }) }) describe('getCapabilities', () => { it('allows to grab certain capabilities', async () => { const configParser = new ConfigParser(FIXTURES_CONF) await configParser.initialize() configParser['_capabilities'] = [ { browserName: 'foo' }, { browserName: 'bar' } ] expect(configParser.getCapabilities()).toEqual(configParser['_capabilities']) expect(configParser.getCapabilities(1)).toEqual({ browserName: 'bar' }) }) }) describe('getSpecs', () => { it('should exclude files', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize() const specs = configParser.getSpecs() expect(specs).toContain(__filename) expect(specs).not.toContain(path.join(__dirname, 'validateConfig.test.ts')) }) it('should exclude files from arrays', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF_ARRAY) await configParser.initialize() const specs = configParser.getSpecs() expect(specs[0]).toContain(__filename) expect(specs[0]).not.toContain(path.join(__dirname, 'validateConfig.test.ts')) }) it('should exclude/include capability excludes', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize() const specs = configParser.getSpecs([INDEX_PATH], [__filename]) expect(specs).not.toContain(__filename) expect(specs).not.toContain(path.join(__dirname, 'validateConfig.test.ts')) expect(specs).toContain(INDEX_PATH) }) it('should exclude/include capability excludes in suites', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ suite: ['unit', 'mobile'] }) const configParserPath = path.join(__dirname, 'configparser.test.ts') const requireLibPath = path.join(__dirname, 'FileSystemPathService.test.ts') const getSpecs = () => configParser.getSpecs([INDEX_PATH], [requireLibPath]) expect(getSpecs()).toContain(configParserPath) // verify that the capability exclude is applied successfully // when the config exclude is not defined configParser['_config'].exclude = [] expect(getSpecs()).not.toContain(requireLibPath) expect(getSpecs()).toContain(configParserPath) }) it('should include typescript files', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize() const tsFile = path.resolve(FIXTURES_PATH, '*.ts') const specs = configParser.getSpecs([tsFile]) expect(specs).toContain(path.resolve(FIXTURES_PATH, 'typescript.ts')) }) it('should include es6 files', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize() const es6File = path.resolve(FIXTURES_PATH, '*.es6') const specs = configParser.getSpecs([es6File]) expect(specs).toContain(path.resolve(FIXTURES_PATH, 'test.es6')) }) it('should include mjs files', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize() const mjsFile = path.resolve(FIXTURES_PATH, '*.mjs') const specs = configParser.getSpecs([mjsFile]) expect(specs).toContain(path.resolve(FIXTURES_PATH, 'test.mjs')) }) it('should include cjs files', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize() const cjsFile = path.resolve(FIXTURES_PATH, '*.cjs') const specs = configParser.getSpecs([cjsFile]) expect(specs).toContain(path.resolve(FIXTURES_PATH, 'test.cjs')) }) it('should include files in arrays to be run in a single worker', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF_ARRAY) await configParser.initialize() const specs = configParser.getSpecs() expect(Array.isArray(specs[0])).toBe(true) // Answer here is 3 because FileSystemPathService.test.ts is not included // in MockedFileSystem_LoadingAsMuchAsCanFromFileSystem expect(specs[0].length).toBe(3) }) it('should handle grouped specs in suites', async () => { // const configParser = await ConfigParserForTest() const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF_ARRAY) await configParser.initialize({ suite: ['functional'] }) const specs = configParser.getSpecs() expect(specs[0]).not.toContain(path.join(__dirname, '..', 'validateConfig.test.ts')) expect(specs[0]).toContain(INDEX_PATH) expect(specs[1].length).toBe(3) }) it('should not include other file types', async () => { const configParser = await ConfigParserForTest(FIXTURES_CONF) await configParser.initialize() const javaFile = path.resolve(FIXTURES_PATH, '*.java') const specs = configParser.getSpecs([javaFile]) expect(specs).not.toContain(javaFile) }) it('should include spec when specifying a suite', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ suite: ['mobile'], spec: [INDEX_PATH] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(2) expect(specs).toContain(INDEX_PATH) expect(specs).toContain(path.join(__dirname, 'FileSystemPathService.test.ts')) }) it('should repeat spec 3 times', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: [INDEX_PATH], repeat: 3 }) const specs = configParser.getSpecs() expect(specs).toHaveLength(3) expect(specs).toStrictEqual([ INDEX_PATH, INDEX_PATH, INDEX_PATH, ]) }) it('should not include spec if blank spec parameter passed', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ suite: ['mobile'], spec: [] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(1) }) it('should repeat specs from suite 3 times', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ suite: ['functional'], repeat: 3 }) const specs = configParser.getSpecs() expect(specs).toHaveLength(3) }) it('should repeat specs in specific order to fail early', async () => { const spec1 = path.resolve(__dirname, '../utils.test.ts') const spec2 = path.resolve(__dirname, 'configparser.test.ts') const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ spec: [spec1, spec2], repeat: 3 }) const specs = configParser.getSpecs() expect(specs).toEqual([ spec1, spec2, spec1, spec2, spec1, spec2, ]) }) it('should throw an error if repeat is set but no spec or suite is specified', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF) await configParser.initialize({ repeat: 3 }) expect(() => configParser.getSpecs()).toThrow('The --repeat flag requires that either the --spec or --suite flag is also set') }) it('should include spec when specifying a suite unless excluded', async () => { const configParser = ConfigParserBuilder .withBaseDir( path.resolve(__dirname), path.resolve(FIXTURES_PATH, 'sut-config') ) .withFiles([ ...(await MockedFileSystem_LoadingAsMuchAsCanFromFileSystem()), FileNamed(path.resolve(FIXTURES_PATH, 'sut-config')).withContents((await MockFileContentBuilder.FromRealConfigFile(FIXTURES_CONF)).withTheseContentsMergedOn({ specs: [path.join(__dirname, '*.test.ts')], config: { exclude: [ // FIXTURES_CONF already excludes validateConfig path.resolve(__dirname, 'ganondorf.test.ts') ], suites: { mobile: [ path.resolve(__dirname, 'FileSystemPathService.test.ts'), path.resolve(__dirname, 'validateConfig.test.ts'), path.resolve(__dirname, 'link.test.ts'), path.resolve(__dirname, 'ganondorf.test.ts') ] } } }).build()), FileNamed(path.resolve(__dirname, 'link.test.ts')).withContents(''), FileNamed(path.resolve(__dirname, 'ganondorf.test.ts')).withContents(''), FileNamed(path.resolve(__dirname, 'zelda.test.ts')).withContents('') ]).build() await configParser.initialize({ suite: ['mobile'], spec: [INDEX_PATH] }) const specs = configParser.getSpecs() expect(specs).toHaveLength(3) expect(specs).toContain(INDEX_PATH) expect(specs).toContain(path.join(__dirname, 'FileSystemPathService.test.ts')) expect(specs).toContain(path.resolve(__dirname, 'link.test.ts')) expect(specs).not.toContain(path.join(__dirname, 'validateConfig.test.ts')) expect(specs).not.toContain(path.resolve(__dirname, 'ganondorf.test.ts')) expect(specs).not.toContain(path.resolve(__dirname, 'zelda.test.ts')) }) it("should throw if suite doesn't exist or doesn't contain any files", async () => { const configParser = ConfigParserBuilder .withBaseDir( path.join(process.cwd(), '/workdir/'), 'conf-under-test' ) .withFiles( [ FileNamed(path.join(process.cwd(), '/workdir/conf-under-test')).withContents({ config: { user: 'foobar', key: '50fa142c-3121-4gb0-9p07-8q326vvbq7b0', specs: [path.join(__dirname, '/tests/*.test.ts')], exclude: [ path.join(__dirname, '/tests/and-this-test-two.test.ts') ], capabilities: [{ browserName: 'chrome' }], suites: { unit: [path.join(__dirname, '/tests/node/configparser.test.ts')], mobile: [path.join(__dirname, '/tests/node/FileSystemPathService.test.ts')], functional: [ path.join(__dirname, '/tests/validateConfig.test.ts'), path.join(__dirname, '/tests/..', 'src/index.ts') ], 'something': [] }, suite: ['something'] } }), FileNamed(path.join(__dirname, 'tests', 'only-this-test-one.test.ts')).withContents('what1') ] ).build() await configParser.initialize() // eslint-disable-next-line no-useless-escape expect(() => configParser.getSpecs()).toThrowError('The suite(s) \"something\" you specified don\'t exist in your config file or doesn\'t contain any files!') configParser['merge']({ suite: ['something-else'], spec: [path.join(__dirname, 'tests', 'only-this-test-one.test.ts')] }) // eslint-disable-next-line no-useless-escape expect(() => configParser.getSpecs()).toThrowError('The suite(s) \"something\", \"something-else\" you specified don\'t exist in your config file or doesn\'t contain any files!') }) }) describe('getFilePaths', () => { it('should include files in arrays to be run in a single worker', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF_ARRAY) await configParser.initialize() const filePaths = ConfigParser.getFilePaths(configParser['_config'].specs!, FIXTURES_CONF_ARRAY, configParser['_pathService']) expect(Array.isArray(filePaths[0])).toBe(true) expect(filePaths[0].length).toBe(4) expect(filePaths[0][0]).not.toContain('*') }) }) it('shard', async () => { const configParser = await ConfigParserForTestWithAllFiles(FIXTURES_CONF_ARRAY) let specs: any = ['1', '2', '3', '4', '5'] await configParser.initialize({ shard: { current: 1, total: 3 } }) expect(configParser.shard(specs)).toEqual(['1', '2']) await configParser.initialize({ shard: { current: 2, total: 3 } }) expect(configParser.shard(specs)).toEqual(['3', '4']) await configParser.initialize({ shard: { current: 3, total: 3 } }) expect(configParser.shard(specs)).toEqual(['5']) specs = ['1', ['2.1', '2.2', '2.3'], '3', ['4.1', '4.2']] await configParser.initialize({ shard: { current: 1, total: 3 } }) expect(configParser.shard(specs)).toEqual(['1']) await configParser.initialize({ shard: { current: 2, total: 3 } }) expect(configParser.shard(specs)).toEqual([['2.1', '2.2', '2.3']]) await configParser.initialize({ shard: { current: 3, total: 3 } }) expect(configParser.shard(specs)).toEqual(['3', ['4.1', '4.2']]) }) })