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}
` }