import fs from 'node:fs/promises'
import url from 'node:url'
import path from 'node:path'
import logger from '@wdio/logger'
import { resolve } from 'import-meta-resolve'
import type { InlineConfig } from 'vite'
import { WDIO_EVENT_NAME } from '../constants.js'
import { MOCHA_VARIABELS } from '../constants.js'
import type { Environment, FrameworkPreset } from '../types.js'
const log = logger('@wdio/browser-runner')
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
export async function getTemplate(options: WebdriverIO.BrowserRunnerOptions, env: Environment, spec: string, p = process) {
const root = options.rootDir || process.cwd()
const isHeadless = options.headless || Boolean(process.env.CI)
const alias = (options.viteConfig as (InlineConfig | undefined))?.resolve?.alias || {}
const usesTailwindCSS = await hasFileByExtensions(path.join(root, 'tailwind.config'))
/**
* clean up some values that might cause serialization issues
*/
if ('runner' in env.config) {
delete env.config.runner
}
let sourceMapScript = ''
let sourceMapSetupCommand = ''
try {
const sourceMapSupportDir = await resolve('source-map-support', import.meta.url)
sourceMapScript = /*html*/``
sourceMapSetupCommand = 'sourceMapSupport.install()'
} catch (err) {
log.error(`Failed to setup source-map-support: ${(err as Error).message}`)
}
return /* html */`
WebdriverIO Browser Test
${usesTailwindCSS ? /*html*/'' : ''}
${sourceMapScript}
`
}
export async function userfriendlyImport(preset: FrameworkPreset, pkg?: string) {
if (!pkg) {
return {}
}
try {
return await import(pkg)
} catch {
throw new Error(
`Couldn't load preset "${preset}" given important dependency ("${pkg}") is not installed.\n` +
`Please run:\n\n\tnpm install ${pkg}\n\tor\n\tyarn add --dev ${pkg}`
)
}
}
export function normalizeId(id: string, base?: string): string {
if (base && id.startsWith(base)) {
id = `/${id.slice(base.length)}`
}
return id
.replace(/^\/@id\/__x00__/, '\0') // virtual modules start with `\0`
.replace(/^\/@id\//, '')
.replace(/^__vite-browser-external:/, '')
.replace(/^node:/, '')
.replace(/[?&]v=\w+/, '?') // remove ?v= query
.replace(/\?$/, '') // remove end query mark
}
export async function getFilesFromDirectory (dir: string) {
/**
* check if dir exists
*/
const isExisting = await fs.access(dir).then(() => true, () => false)
if (!isExisting) {
return []
}
let files = await fs.readdir(dir)
files = (await Promise.all(files.map(async file => {
const filePath = path.join(dir, file)
const stats = await fs.stat(filePath)
if (stats.isDirectory()) {
return getFilesFromDirectory(filePath)
} else if (stats.isFile()) {
return filePath
}
}))).filter(Boolean) as string[]
return files.reduce((all, folderContents) => all.concat(folderContents), [] as string[])
}
let mockedModulesList: ([string, string])[]
export async function getManualMocks(automockDir: string) {
/**
* read available mocks only one time
*/
if (!mockedModulesList) {
mockedModulesList = (await getFilesFromDirectory(automockDir))
/**
* seperate to module name and actual path
*/
.map((filePath) => [
filePath,
filePath.slice(automockDir.length + 1).slice(0, -path.extname(filePath).length)
])
}
return mockedModulesList
}
const EXTENSION = ['.js', '.ts', '.mjs', '.cjs', '.mts']
export async function hasFileByExtensions (p: string, extensions = EXTENSION) {
return (await Promise.all([
fs.access(p).then(() => p, () => undefined),
...extensions.map((ext) => fs.access(p + ext).then(() => p + ext, () => undefined))
])).filter(Boolean)[0]
}
export function hasDir (p: string) {
return fs.stat(p).then((s) => s.isDirectory(), () => false)
}
export function getErrorTemplate(filename: string, error: Error) {
return /*html*/`
${error.stack}
`
}