@allwright.dev/vitest

中文文档
0.2.0发布于 2026/10/17MITTypeScriptES Module构建来源已验证近 30 天下载 1948

Vitest fixtures for the allwright TypeScript client.

原始使用文档

@allwright.dev/vitest

Vitest fixtures for allwright with Playwright-style browser and page injection, plus Android, iOS, macOS, and Windows app fixtures for native and hybrid tests.

Wrap the Vitest configuration with allwrightVitestConfig. It starts Allwright once before workers run and shuts it down once after the complete test run. Existing user global setup files remain supported and run after Allwright starts, then tear down before Allwright stops.

import { allwrightVitestConfig } from "@allwright.dev/vitest/config";
import { defineConfig } from "vitest/config";

export default allwrightVitestConfig(defineConfig({
  test: {
    globalSetup: ["./test/my-global-setup.ts"],
  },
}));

Install:

bun add -d vitest @allwright.dev/vitest
import { expect, test } from "@allwright.dev/vitest";

test("opens a page", async ({ page }) => {
  await page.goto("https://themoderninternet.vercel.app");
  await page.click(
    "xpath=//div[contains(@class,'card')][.//h2[normalize-space()='Form Inputs']]//button[normalize-space()='Visit page']",
  );
  await expect(page.locator('xpath=//h1[text()="Form Inputs"]')).toHaveText("Form Inputs");
});

test("opens an Android app", async ({ androidApp }) => {
  await androidApp.click('Id=com.example.airticket:id/bottom_nav_account');
});

test("opens an iOS app", async ({ iosApp }) => {
  await iosApp.getByRole("button", { name: "Login", exact: true }).click();
});

test("opens a macOS app", async ({ macosApp }) => {
  await macosApp.getByRole("button", { name: "New Document", exact: true }).click();
});

test("opens a Windows app", async ({ windowsApp }) => {
  await windowsApp.getByRole("textbox").fill("Hello from Allwright");
});

Checked-in example specs cover every implemented surface: Web, Android, iOS, macOS, and Windows. Native specs use the app target configured for their surface in allwright.config.yaml or allwright.config.json.

The fixture package reads the shared stack-agnostic config format through @allwright.dev/core. Use allwright.config.yaml by default, or allwright.config.json if you prefer. Both follow the same root schema in allwright.schema.json.

schemaVersion: 1

server:
  addr: 127.0.0.1:50051

web:
  browser:
    name: firefox
    binary: /Applications/Firefox.app/Contents/MacOS/firefox

expect:
  timeoutMs: 7000
  intervalMs: 100

suites:
  smoke:
    web:
      browser:
        name: chromium

Vitest can override or select from that shared config without switching to a Vitest-specific file:

import { expect, test as base } from "@allwright.dev/vitest";

const test = base.extend({
  allwright: async ({}, use) => {
    await use({ suite: "smoke" });
  }
});

Android fixtures are available alongside the web fixtures:

import { expect, test as base } from "@allwright.dev/vitest";

const test = base.extend({
  allwright: async ({}, use) => {
    await use({
      android: {
        launchOptions: {
          apkPath: "/absolute/path/to/app.apk",
          appId: "com.example.airticket",
        },
      },
    });
  },
});

test("android only", async ({ androidApp }) => {
  await androidApp.fill('xpath=//*[@text="Email"]', "[email protected]");
  await expect(androidApp.locator('xpath=//*[@text="Email"]')).toHaveText("[email protected]");
});

test("hybrid web and android", async ({ page, androidApp }) => {
  await page.goto("https://themoderninternet.vercel.app");
  await androidApp.click('Id=com.example.airticket:id/bottom_nav_account');
});

iOS uses the same app and locator shape. Configure the native agent endpoint and bundle identifier once, then keep platform bootstrap out of test bodies:

# allwright.config.yaml
mobile:
  ios:
    device: iPhone 17 Pro
    app:
      binary: https://allwright.dev/Flights-simulator.ipa

Set ALLWRIGHT_IOS_AGENT_ENDPOINT when the agent is not reachable at http://127.0.0.1:8100. The app value may also be a local .app, .zip, or .ipa; the fixture downloads/extracts it when needed, installs it on the selected simulator, derives its bundle identifier, and launches it. Override it with ALLWRIGHT_IOS_APP_PATH. No manual installation or polling belongs in the test. The test remains platform-client code only:

import { expect, test } from "@allwright.dev/vitest";

test("logs in", async ({ iosApp }) => {
  await iosApp.getByRole("button", { name: "Login", exact: true }).click();
  await iosApp.getByLabel("Email", { exact: true }).fill("[email protected]");
  await expect(iosApp.getByText("Welcome back", { exact: true })).toBeVisible();
});

macOS apps use an installed application bundle identifier. The fixture starts the bundled desktop XCUITest runner and launches the configured app lazily:

# allwright.config.yaml
desktop:
  mac:
    app:
      id: com.apple.TextEdit
import { expect, test } from "@allwright.dev/vitest";

test("opens a document window", async ({ macosApp }) => {
  await macosApp.getByRole("button", { name: "New Document", exact: true }).click();
  await expect(macosApp.getByRole("textField")).toBeVisible();
});

Use ALLWRIGHT_MAC_AGENT_ENDPOINT for an external agent or ALLWRIGHT_MAC_APP_ID to override the configured bundle identifier.

Windows apps use an executable path, command name, or packaged-app AUMID. The fixture starts the bundled FlaUI UIA3 agent and launches the app lazily:

# allwright.config.yaml
desktop:
  windows:
    app:
      id: notepad.exe
import { expect, test } from "@allwright.dev/vitest";

test("edits a document", async ({ windowsApp }) => {
  await windowsApp.getByRole("textbox").fill("Hello from Allwright");
  await expect(windowsApp.getByRole("textbox")).toContainText("Hello");
});

Use ALLWRIGHT_WINDOWS_AGENT_ENDPOINT for an external agent or ALLWRIGHT_WINDOWS_APP_ID to override the configured application identifier.

Fixtures are lazy on first use. Injecting browser, page, android, androidApp, ios, iosApp, macos, macosApp, windows, or windowsApp does not launch or connect immediately; the underlying session is created only when the test first performs an action through that fixture. Sync metadata properties are only available after the lazy fixture has been realized by a prior awaited call.

Available fixtures:

  • browser: launched web browser
  • page: initial web page
  • android: connected Android device session
  • androidApp: launched Android app page
  • ios: connected iOS device session
  • iosApp: launched iOS app page
  • macos: connected macOS desktop session
  • macosApp: launched macOS app page
  • windows: connected Windows desktop session
  • windowsApp: launched Windows app page

androidApp launches using allwright.android.launchOptions first, then falls back to config.mobile.android. iosApp does the same with allwright.ios.launchOptions and config.mobile.ios. macosApp does the same with allwright.macos.launchOptions and config.desktop.mac. windowsApp does the same with allwright.windows.launchOptions and config.desktop.windows.

Android, iOS, macOS, and Windows apps and locators support the native action and expectation subset: click, count, focus, fill, press, textContent, innerText, waitForSelector, and screenshot. Mobile expectations provide toHaveText, toContainText, toHaveCount, and toBeVisible with the same retry controls as web fixtures. Hover and highlight remain web-only.

The opt-in accessibility integration test exercises the public page fixture, bundled protobuf, running engine and dynamically loaded web plugin. From this repository, after building the packages, run:

ALLWRIGHT_TEST_BROWSER=chromium bun run --cwd typescript/vitest vitest run tests/accessibility.spec.ts
ALLWRIGHT_TEST_BROWSER=firefox bun run --cwd typescript/vitest vitest run tests/accessibility.spec.ts

Set ALLWRIGHT_TEST_LIVE_SITE=1 to reproduce the Form Inputs navigation on the public example site. The default fixture is a local data URL. Use ALLWRIGHT_CLI_PATH and ALLWRIGHT_SERVER_ADDR to select the runtime under test; client, engine and installed web plugin versions must match.

Negated assertions and locator exclusion

Every Allwright page/app and locator matcher supports both Vitest's .not form and the callable .not() alias. Negation retries the opposite condition using the same timeout and interval options:

await expect(page.getByRole('status')).not.toHaveText('Loading');
await expect(page.getByTestId('spinner')).not().toBeVisible();
await expect(page).not.toHaveCount('.error', 1);
await expect(androidApp.locator('text="Loading"')).not().toBeVisible();

Negative visibility succeeds when the element is hidden or absent. Selector, transport, and session errors still fail; they are not evidence of invisibility. Negative text assertions require a successful text read. Matcher objects are immutable, and a second .not toggles back to positive assertions. Ordinary Vitest value assertions retain their native .not syntax.

Web locators also support exclusion by element identity, with further chaining:

const enabledActions = page.getByRole('button')
  .not(page.getByRole('button', { disabled: true }));
await expect(enabledActions).not().toHaveCount(0);

Both locators must belong to the same page. .not(other) removes matching nodes; filter({ hasNot: other }) removes candidates containing matching descendants. The same exclusion API is available in the core web clients as TypeScript/Java .not(other), Go .Not(other), Rust .not(&other), and Python .not_(other). Native Android locator exclusion is not exposed by this web selector API.

Retrying state expectations

Assert against the page or locator so each attempt reads fresh state:

await expect(page).toHaveURL('https://example.com/dashboard');
await expect(page).toHaveURL(/\/dashboard(?:\?|$)/, { timeoutMs: 10_000 });
await expect(page.locator('input')).toHaveValue(/ready/i);
await expect(page.locator('select')).toHaveSelectedOptions(['a', 'b']);
await expect(page.locator('select')).toHaveSelectedOptions([{ value: 'a', label: /Alpha/ }]);
await expect(page.locator('textarea')).toHaveSelectedText('selected words');
await expect(page.locator('input[type=checkbox]')).toBeChecked();
await expect(page.locator('button[type=submit]')).toBeEnabled();
await expect(page.locator('input')).toBeEditable();
await expect(page.locator('input')).toBeFocused();
await expect(page.locator('.spinner')).toBeHidden();
await expect(page.locator('main')).toBeAttached();
await expect(page.locator('h1')).toHaveText('Welcome');
await expect(page.locator('a')).toHaveAttribute('href', /dashboard/);
await expect(page.locator('button')).toHaveBoundingBox({ width: 100, height: 40 });
await expect(page.locator('input[type=radio]')).not().toBeChecked();

Every element matcher also has a page/app form, such as expect(page).toHaveValue('#name', 'Alice') or expect(androidApp).toBeEnabled('text="Continue"'). Value, checked-state, attribute, bounding-box, attached/hidden, enabled/disabled, focused, and editable matchers work across web, Android, iOS, macOS, and Windows. URL, selected-option, and selected-text matchers remain web-only.

All Allwright page/app/locator expectations retry until they match or the timeout expires, including .not and .not(). Defaults come from the shared assertion configuration, falling back to 5 seconds and a 100 ms interval. Override these with { timeoutMs, intervalMs }. In-flight reads and nested command timeout hints are bounded by the remaining assertion budget; timeoutMs: 0 makes one observation without polling. Ordinary assertions on already captured values, such as expect(await page.url()).toBe(...), use standard Vitest behavior and do not re-read the page.

String URL expectations match the complete URL exactly; regex expectations match the current URL on every attempt without modifying the regex's lastIndex. Values, selected text, and attributes also accept strings or regexes. null asserts a missing attribute or unsupported textbox selection; an empty string is distinct. Selected-option arrays match in order and require the same number of options. Entries can be exact values, value regexes, or objects specifying value, label, and/or index. Bounding-box expectations compare only the supplied coordinates/dimensions exactly in viewport CSS pixels; null expects no visible box. .not.toBeChecked() expects an unchecked control. Missing elements and read errors cannot satisfy negation.

版本与发布记录

@allwright.dev/vitest 共发布 74 个版本,首次发布于 2026/08/26;过去 12 个月发布了 74 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.0latest2026/10/1722 天前
0.1.352026/10/1722 天前
0.1.342026/10/1722 天前
0.1.332026/10/1722 天前
0.1.322026/10/1622 天前
0.1.312026/10/1622 天前

关于 @allwright.dev/vitest 的常见问题

@allwright.dev/vitest 是什么?

Vitest fixtures for the allwright TypeScript client.

如何安装 @allwright.dev/vitest?

在项目目录运行 npm install @allwright.dev/vitest,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vitest(^3.2.4)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@allwright.dev/vitest 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@allwright.dev/vitest 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@allwright.dev/vitest 可以商用吗?

@allwright.dev/vitest 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@allwright.dev/vitest 的下载量有多少?

根据 npm 官方下载统计,@allwright.dev/vitest 在 2026/10/06 – 2026/11/04 这 30 天内被下载 1,948 次,日均约 65 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@allwright.dev/vitest 还在维护吗?

最新版本 0.2.0 发布于 2026/10/17(22 天前);至今共发布 74 个版本,其中过去 12 个月发布了 74 个;首次发布于 2026/08/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。