@vitest/browser
Advanced tools
Comparing version 2.0.0-beta.2 to 2.0.0-beta.3
@@ -1,3 +0,4 @@ | ||
import { Plugin } from 'vite'; | ||
import { WorkspaceProject } from 'vitest/node'; | ||
export { BrowserCommand } from 'vitest/node'; | ||
import { Plugin } from 'vitest/config'; | ||
@@ -4,0 +5,0 @@ declare const _default: (project: WorkspaceProject, base?: string) => Plugin[]; |
import { fileURLToPath } from 'node:url'; | ||
import { readFile } from 'node:fs/promises'; | ||
import { readFile as readFile$1 } from 'node:fs/promises'; | ||
import sirv from 'sirv'; | ||
@@ -8,2 +8,5 @@ import { coverageConfigDefaults } from 'vitest/config'; | ||
import { esmWalker } from '@vitest/utils/ast'; | ||
import fs, { promises } from 'node:fs'; | ||
import { resolve as resolve$1, dirname } from 'node:path'; | ||
import { isFileServingAllowed } from 'vitest/node'; | ||
@@ -402,2 +405,150 @@ const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//; | ||
function assertFileAccess(path, project) { | ||
if (!isFileServingAllowed(path, project.server) && !isFileServingAllowed(path, project.ctx.server)) | ||
throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`); | ||
} | ||
const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => { | ||
const filepath = resolve$1(dirname(testPath), path); | ||
assertFileAccess(filepath, project); | ||
if (typeof options === "object" && !options.encoding) | ||
options.encoding = "utf-8"; | ||
return promises.readFile(filepath, options); | ||
}; | ||
const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => { | ||
const filepath = resolve$1(dirname(testPath), path); | ||
assertFileAccess(filepath, project); | ||
const dir = dirname(filepath); | ||
if (!fs.existsSync(dir)) | ||
await promises.mkdir(dir, { recursive: true }); | ||
await promises.writeFile(filepath, data, options); | ||
}; | ||
const removeFile = async ({ project, testPath = process.cwd() }, path) => { | ||
const filepath = resolve$1(dirname(testPath), path); | ||
assertFileAccess(filepath, project); | ||
await promises.rm(filepath); | ||
}; | ||
function isObject(payload) { | ||
return payload != null && typeof payload === "object"; | ||
} | ||
function isSendKeysPayload(payload) { | ||
const validOptions = ["type", "press", "down", "up"]; | ||
if (!isObject(payload)) | ||
throw new Error("You must provide a `SendKeysPayload` object"); | ||
const numberOfValidOptions = Object.keys(payload).filter( | ||
(key) => validOptions.includes(key) | ||
).length; | ||
const unknownOptions = Object.keys(payload).filter((key) => !validOptions.includes(key)); | ||
if (numberOfValidOptions > 1) { | ||
throw new Error( | ||
`You must provide ONLY one of the following properties to pass to the browser runner: ${validOptions.join( | ||
", " | ||
)}.` | ||
); | ||
} | ||
if (numberOfValidOptions === 0) { | ||
throw new Error( | ||
`You must provide one of the following properties to pass to the browser runner: ${validOptions.join( | ||
", " | ||
)}.` | ||
); | ||
} | ||
if (unknownOptions.length > 0) | ||
throw new Error(`Unknown options \`${unknownOptions.join(", ")}\` present.`); | ||
return true; | ||
} | ||
function isTypePayload(payload) { | ||
return "type" in payload; | ||
} | ||
function isPressPayload(payload) { | ||
return "press" in payload; | ||
} | ||
function isDownPayload(payload) { | ||
return "down" in payload; | ||
} | ||
function isUpPayload(payload) { | ||
return "up" in payload; | ||
} | ||
const sendKeys = async ({ provider }, payload) => { | ||
if (!isSendKeysPayload(payload) || !payload) | ||
throw new Error("You must provide a `SendKeysPayload` object"); | ||
if (provider.name === "playwright") { | ||
const page = provider.page; | ||
if (isTypePayload(payload)) | ||
await page.keyboard.type(payload.type); | ||
else if (isPressPayload(payload)) | ||
await page.keyboard.press(payload.press); | ||
else if (isDownPayload(payload)) | ||
await page.keyboard.down(payload.down); | ||
else if (isUpPayload(payload)) | ||
await page.keyboard.up(payload.up); | ||
} else if (provider.name === "webdriverio") { | ||
const browser = provider.browser; | ||
if (isTypePayload(payload)) | ||
await browser.keys(payload.type.split("")); | ||
else if (isPressPayload(payload)) | ||
await browser.keys([payload.press]); | ||
else | ||
throw new Error('Only "press" and "type" are supported by webdriverio.'); | ||
} else { | ||
throw new Error(`"sendKeys" is not supported for ${provider.name} browser provider.`); | ||
} | ||
}; | ||
var builtinCommands = { | ||
readFile, | ||
removeFile, | ||
writeFile, | ||
sendKeys | ||
}; | ||
const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context"; | ||
const ID_CONTEXT = "@vitest/browser/context"; | ||
function BrowserContext(project) { | ||
project.config.browser.commands ??= {}; | ||
for (const [name, command] of Object.entries(builtinCommands)) | ||
project.config.browser.commands[name] ??= command; | ||
for (const command in project.config.browser.commands) { | ||
if (!/^[a-z_$][\w$]*$/i.test(command)) | ||
throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`); | ||
} | ||
return { | ||
name: "vitest:browser:virtual-module:context", | ||
enforce: "pre", | ||
resolveId(id) { | ||
if (id === ID_CONTEXT) | ||
return VIRTUAL_ID_CONTEXT; | ||
}, | ||
load(id) { | ||
if (id === VIRTUAL_ID_CONTEXT) | ||
return generateContextFile(project); | ||
} | ||
}; | ||
} | ||
function generateContextFile(project) { | ||
const commands = Object.keys(project.config.browser.commands ?? {}); | ||
const filepathCode = "__vitest_worker__.filepath || __vitest_worker__.current?.file?.filepath || undefined"; | ||
const commandsCode = commands.map((command) => { | ||
return ` ["${command}"]: (...args) => rpc().triggerCommand("${command}", ${filepathCode}, args),`; | ||
}).join("\n"); | ||
return ` | ||
const rpc = () => __vitest_worker__.rpc | ||
export const server = { | ||
platform: ${JSON.stringify(process.platform)}, | ||
version: ${JSON.stringify(process.version)}, | ||
provider: ${JSON.stringify(project.browserProvider.name)}, | ||
commands: { | ||
${commandsCode} | ||
} | ||
} | ||
export const commands = server.commands | ||
export const page = { | ||
get config() { | ||
return __vitest_browser_runner__.config | ||
} | ||
} | ||
`; | ||
} | ||
var index = (project, base = "/") => { | ||
@@ -418,5 +569,5 @@ const pkgRoot = resolve(fileURLToPath(import.meta.url), "../.."); | ||
async configureServer(server) { | ||
const testerHtml = readFile(resolve(distRoot, "client/tester.html"), "utf8"); | ||
const runnerHtml = readFile(resolve(distRoot, "client/index.html"), "utf8"); | ||
const injectorJs = readFile(resolve(distRoot, "client/esm-client-injector.js"), "utf8"); | ||
const testerHtml = readFile$1(resolve(distRoot, "client/tester.html"), "utf8"); | ||
const runnerHtml = readFile$1(resolve(distRoot, "client/index.html"), "utf8"); | ||
const injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8"); | ||
const favicon = `${base}favicon.svg`; | ||
@@ -568,2 +719,3 @@ const testerPrefix = `${base}__vitest_test__/__test__/`; | ||
}, | ||
BrowserContext(project), | ||
{ | ||
@@ -607,3 +759,3 @@ name: "vitest:browser:esm-injector", | ||
function replacer(code, values) { | ||
return code.replace(/{\s*(\w+)\s*}/g, (_, key) => values[key] ?? ""); | ||
return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? ""); | ||
} | ||
@@ -610,0 +762,0 @@ async function formatScripts(scripts, server) { |
const playwrightBrowsers = ["firefox", "webkit", "chromium"]; | ||
class PlaywrightBrowserProvider { | ||
name = "playwright"; | ||
cachedBrowser = null; | ||
cachedPage = null; | ||
browser; | ||
browser = null; | ||
page = null; | ||
browserName; | ||
ctx; | ||
@@ -14,17 +14,17 @@ options; | ||
this.ctx = project; | ||
this.browser = browser; | ||
this.browserName = browser; | ||
this.options = options; | ||
} | ||
async openBrowserPage() { | ||
if (this.cachedPage) | ||
return this.cachedPage; | ||
if (this.page) | ||
return this.page; | ||
const options = this.ctx.config.browser; | ||
const playwright = await import('playwright'); | ||
const browser = await playwright[this.browser].launch({ | ||
const browser = await playwright[this.browserName].launch({ | ||
...this.options?.launch, | ||
headless: options.headless | ||
}); | ||
this.cachedBrowser = browser; | ||
this.cachedPage = await browser.newPage(this.options?.page); | ||
return this.cachedPage; | ||
this.browser = browser; | ||
this.page = await browser.newPage(this.options?.page); | ||
return this.page; | ||
} | ||
@@ -36,6 +36,6 @@ async openPage(url) { | ||
async close() { | ||
const page = this.cachedPage; | ||
this.cachedPage = null; | ||
const browser = this.cachedBrowser; | ||
this.cachedBrowser = null; | ||
const page = this.page; | ||
this.page = null; | ||
const browser = this.browser; | ||
this.browser = null; | ||
await page?.close(); | ||
@@ -49,4 +49,4 @@ await browser?.close(); | ||
name = "webdriverio"; | ||
cachedBrowser = null; | ||
browser; | ||
browser = null; | ||
browserName; | ||
ctx; | ||
@@ -59,10 +59,10 @@ options; | ||
this.ctx = ctx; | ||
this.browser = browser; | ||
this.browserName = browser; | ||
this.options = options; | ||
} | ||
async openBrowser() { | ||
if (this.cachedBrowser) | ||
return this.cachedBrowser; | ||
if (this.browser) | ||
return this.browser; | ||
const options = this.ctx.config.browser; | ||
if (this.browser === "safari") { | ||
if (this.browserName === "safari") { | ||
if (options.headless) | ||
@@ -72,3 +72,3 @@ throw new Error("You've enabled headless mode for Safari but it doesn't currently support it."); | ||
const { remote } = await import('webdriverio'); | ||
this.cachedBrowser = await remote({ | ||
this.browser = await remote({ | ||
...this.options, | ||
@@ -78,3 +78,3 @@ logLevel: "error", | ||
}); | ||
return this.cachedBrowser; | ||
return this.browser; | ||
} | ||
@@ -84,3 +84,3 @@ buildCapabilities() { | ||
...this.options?.capabilities, | ||
browserName: this.browser | ||
browserName: this.browserName | ||
}; | ||
@@ -93,3 +93,3 @@ const headlessMap = { | ||
const options = this.ctx.config.browser; | ||
const browser = this.browser; | ||
const browser = this.browserName; | ||
if (browser !== "safari" && options.headless) { | ||
@@ -109,3 +109,3 @@ const [key, args] = headlessMap[browser]; | ||
await Promise.all([ | ||
this.cachedBrowser?.sessionId ? this.cachedBrowser?.deleteSession?.() : null | ||
this.browser?.sessionId ? this.browser?.deleteSession?.() : null | ||
]); | ||
@@ -112,0 +112,0 @@ process.exit(); |
{ | ||
"name": "@vitest/browser", | ||
"type": "module", | ||
"version": "2.0.0-beta.2", | ||
"version": "2.0.0-beta.3", | ||
"description": "Browser running for Vitest", | ||
@@ -27,2 +27,6 @@ "license": "MIT", | ||
}, | ||
"./context": { | ||
"types": "./context.d.ts", | ||
"default": "./context.js" | ||
}, | ||
"./providers/webdriverio": { | ||
@@ -47,3 +51,3 @@ "types": "./providers/webdriverio.d.ts" | ||
"webdriverio": "*", | ||
"vitest": "2.0.0-beta.2" | ||
"vitest": "2.0.0-beta.3" | ||
}, | ||
@@ -64,3 +68,3 @@ "peerDependenciesMeta": { | ||
"sirv": "^2.0.4", | ||
"@vitest/utils": "2.0.0-beta.2" | ||
"@vitest/utils": "2.0.0-beta.3" | ||
}, | ||
@@ -75,6 +79,6 @@ "devDependencies": { | ||
"webdriverio": "^8.36.1", | ||
"@vitest/ui": "2.0.0-beta.2", | ||
"@vitest/ws-client": "2.0.0-beta.2", | ||
"@vitest/runner": "2.0.0-beta.2", | ||
"vitest": "2.0.0-beta.2" | ||
"@vitest/runner": "2.0.0-beta.3", | ||
"@vitest/ui": "2.0.0-beta.3", | ||
"@vitest/ws-client": "2.0.0-beta.3", | ||
"vitest": "2.0.0-beta.3" | ||
}, | ||
@@ -81,0 +85,0 @@ "scripts": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
899046
22
4774
6
+ Added@vitest/expect@2.0.0-beta.3(transitive)
+ Added@vitest/runner@2.0.0-beta.3(transitive)
+ Added@vitest/snapshot@2.0.0-beta.3(transitive)
+ Added@vitest/spy@2.0.0-beta.3(transitive)
+ Added@vitest/utils@2.0.0-beta.3(transitive)
+ Addedtinypool@0.9.0(transitive)
+ Addedvite-node@2.0.0-beta.3(transitive)
+ Addedvitest@2.0.0-beta.3(transitive)
- Removed@vitest/expect@2.0.0-beta.2(transitive)
- Removed@vitest/runner@2.0.0-beta.2(transitive)
- Removed@vitest/snapshot@2.0.0-beta.2(transitive)
- Removed@vitest/spy@2.0.0-beta.2(transitive)
- Removed@vitest/utils@2.0.0-beta.2(transitive)
- Removedtinypool@0.8.4(transitive)
- Removedvite-node@2.0.0-beta.2(transitive)
- Removedvitest@2.0.0-beta.2(transitive)
Updated@vitest/utils@2.0.0-beta.3