import fs from 'node:fs/promises' import semver from 'semver' import { resolve } from 'import-meta-resolve' import { vi, test, expect, beforeEach, afterEach, describe, it } from 'vitest' import { Command } from 'commander' import { execSync } from 'node:child_process' import { execa } from 'execa' import { run, createWebdriverIO } from '../src/index.js' import { runProgram } from '../src/utils.js' import type { ProgramOpts } from '../src/types.js' import { getInstallCommand, installPackages } from '../src/install.js' vi.mock('node:fs/promises', () => ({ default: { access: vi.fn(), mkdir: vi.fn(), writeFile: vi.fn() } })) vi.mock('node:child_process', () => ({ execSync: vi.fn() })) vi.mock('import-meta-resolve', () => ({ resolve: vi.fn() })) vi.mock('commander') vi.mock('semver', () => ({ default: { satisfies: vi.fn().mockReturnValue(true) } })) vi.mock('../src/utils.js', async (importActual) => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports const actual = await importActual() return { ...actual, runProgram: vi.fn(), colorItBold: vi.fn((log) => log), colorIt: vi.fn((log) => log), getPackageVersion: vi.fn().mockReturnValue('0.1.1'), shouldUseYarn: vi.fn().mockReturnValue(true) } }) const consoleLog = console.log.bind(console) beforeEach(() => { console.log = vi.fn() vi.mocked(runProgram).mockClear() vi.mocked(resolve).mockImplementation(() => { throw new Error('foo') }) vi.mocked(execSync).mockReturnValue(` ├── corepack@0.20.0 ├── npm@10.2.0 └── yarn@1.22.19 `) vi.mocked(fs.access).mockResolvedValue() }) test('run', async () => { const op = vi.fn().mockResolvedValue({}) await run(op) expect(op).toBeCalledTimes(1) expect(op).toBeCalledWith('foobar') expect(new Command().arguments).toBeCalledWith('[project-path]') expect(console.log).toBeCalledTimes(1) expect(vi.mocked(console.log).mock.calls[0][0]).toContain('oooooooooooo') expect(vi.mocked(console.log).mock.calls[0][1]).toContain('Next-gen browser and mobile automation') }) test('does not run if Node.js version is too low', async () => { const consoleLog = vi.spyOn(console, 'log') vi.mocked(semver.satisfies).mockReturnValue(false) const op = vi.fn().mockResolvedValue({}) await run(op) expect(op).toBeCalledTimes(0) expect(consoleLog).toBeCalledWith(expect.stringContaining('Please update to Node.js v20 to continue.')) }) test('createWebdriverIO with Yarn', async () => { vi.stubEnv('npm_config_user_agent', 'yarn/4.5.0 npm/? node/v20.11.0 darwin arm64') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'yarn', ['add', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'yarn', ['exec', 'wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('createWebdriverIO with NPM', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('createWebdriverIO with invalid agent should run npm commands', async () => { vi.stubEnv('npm_config_user_agent', 'invalid/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('createWebdriverIO with no npm user agent should run npm commands', async () => { vi.stubEnv('npm_config_user_agent', '') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('createWebdriverIO with pnpm', async () => { vi.stubEnv('npm_config_user_agent', 'pnpm/9.10.0 npm/? node/v20.11.0 darwin arm64') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'pnpm', ['add', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'pnpm', ['exec', 'wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('createWebdriverIO with bun', async () => { vi.stubEnv('npm_config_user_agent', 'bun/1.1.27 npm/? node/v22.6.0 darwin arm64') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'bun', ['install', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'bunx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('creates a directory if it does not exist', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') await createWebdriverIO({ npmTag: 'latest', dev: true } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '--save-dev', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('does not install the @wdio/cli package when the @wdio/cli package is already installed in the current project', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') vi.mocked(resolve).mockReturnValue('/Users/user/dev/my-monorepo/package.json') await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(1) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('does not install the @wdio/cli package when the @wdio/cli package is already installed globally', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') vi.mocked(execSync).mockReturnValue(` ├── @wdio/cli@8.24.3 ├── corepack@0.20.0 ├── npm@10.2.0 └── yarn@1.22.19 `) await createWebdriverIO({ npmTag: 'latest' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(1) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('runs the wdio config command with --yes when the yes option is set to true', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') await createWebdriverIO({ npmTag: 'latest', yes: true } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '@wdio/cli@latest'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--yes', '--npm-tag', 'latest'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) test('does create a package.json to be used by the wdio config command when one does not exist', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') vi.mocked(fs.access).mockRejectedValue(new Error('not existing')) await createWebdriverIO({ npmTag: 'next' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '@wdio/cli@next'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'next'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(1) expect(fs.writeFile).toBeCalledTimes(1) expect(fs.writeFile).toBeCalledWith( expect.any(String), JSON.stringify({ name: 'someProjectName', type: 'module' }, null, 2), ) }) test('installs the next version when the npmTag option is set to "next"', async () => { vi.stubEnv('npm_config_user_agent', 'npm/10.2.4 node/v20.11.0 darwin arm64 workspaces/false') await createWebdriverIO({ npmTag: 'next' } as ProgramOpts) expect(runProgram).toBeCalledWith( 'npm', ['install', '@wdio/cli@next'], expect.any(Object) ) expect(runProgram).toBeCalledWith( 'npx', ['wdio', 'config', '--npm-tag', 'next'], expect.any(Object) ) expect(runProgram).toBeCalledTimes(2) expect(fs.mkdir).toBeCalledTimes(0) expect(fs.writeFile).toBeCalledTimes(0) }) afterEach(() => { vi.mocked(fs.access).mockClear() vi.mocked(fs.mkdir).mockClear() vi.mocked(fs.writeFile).mockClear() vi.mocked(runProgram).mockClear() vi.mocked(resolve).mockClear() vi.mocked(execSync).mockClear() console.log = consoleLog }) vi.mock('execa', () => ({ execa: vi.fn().mockResolvedValue({ stdout: 'foo', stderr: 'bar', exitCode: 0 }) })) vi.mock('detect', () => ({ detect: vi.fn().mockResolvedValue('pnpm') })) describe('install', () => { it('installPackages passing', async () => { expect(await installPackages('/foo/bar', ['foo', 'bar'], true)) .toBe(true) }) it('installPackages failing', async () => { vi.mocked(execa).mockResolvedValue({ stdout: 'foo', stderr: 'bar', exitCode: 1 } as any) expect(await installPackages('/foo/bar', ['foo', 'bar'], false)) .toBe(false) }) it('getInstallCommand', () => { expect(getInstallCommand('pnpm', ['foo', 'bar'], true)) .toBe('pnpm add foo bar --save-dev') }) })