Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ jobs:
needs: detect-changes
if: needs.detect-changes.outputs.code-changed == 'true'
name: Test (${{ matrix.shard }})
env:
# Playwright detects the physical Apple Silicon CPU under Rosetta. Force
# its x64 browser for the x86_64 shard so fspy can inject its x64 preload.
PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: ${{ matrix.target == 'x86_64-apple-darwin' && 'mac15' || '' }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -128,6 +132,13 @@ jobs:
with:
architecture: ${{ matrix.target == 'x86_64-apple-darwin' && 'x64' || '' }}

- name: Install Chromium
run: pnpm --filter vite-task-tools rebuild @playwright/browser-chromium

- name: Install Chromium system dependencies
if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }}
run: pnpm --filter vite-task-tools exec playwright install-deps chromium

- name: Run ignored tests
run: ${{ matrix.cargo_cmd }} test --target ${{ matrix.build_target }} -- --ignored

Expand Down Expand Up @@ -256,6 +267,10 @@ jobs:
- uses: oxc-project/setup-node@4c588e9266bd930b6ddc34307df0659ed511d187 # v1.3.1
if: matrix.mode == 'ignored'

- name: Install Chromium
if: matrix.mode == 'ignored'
run: pnpm --filter vite-task-tools rebuild @playwright/browser-chromium

- name: Run ignored tests
if: matrix.mode == 'ignored'
run: cargo-nextest nextest run --archive-file windows-tests.tar.zst --workspace-remap . --run-ignored ignored-only
Expand All @@ -279,6 +294,9 @@ jobs:
# On musl, concurrent PTY operations can trigger SIGSEGV in musl internals.
# Run test threads sequentially to avoid the race.
RUST_TEST_THREADS: 1
# Playwright's bundled Chromium does not support musl. The browser fixture
# is filtered out there, so avoid downloading an unusable glibc binary.
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
steps:
- name: Install Alpine dependencies
shell: sh {0}
Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- [Rust](https://rustup.rs/) (see [rust-toolchain.toml](rust-toolchain.toml) for the required version)
- [Node.js](https://nodejs.org/) (^20.19.0 || >=22.12.0)
- [pnpm](https://pnpm.io/) (10.x)
- [pnpm](https://pnpm.io/) (11.x)
- [just](https://just.systems/) — task runner for build commands
- [cargo-binstall](https://github.com/cargo-bins/cargo-binstall) — for installing Rust tools

Expand Down Expand Up @@ -51,12 +51,13 @@ UPDATE_SNAPSHOTS=1 cargo test # Update snapshots
Integration tests that need Node.js or the `packages/tools` binaries (e.g. `oxlint`) are marked `#[ignore]` — and `[[e2e]]` cases in e2e snapshots.toml can opt in with `ignore = true`. Default `cargo test` runs only the tests that need nothing beyond the Rust toolchain; to run the rest:

```bash
pnpm install # at the workspace root (installs packages/tools too)
pnpm install # at the workspace root (installs packages/tools and Chromium too)
cargo test -- --include-ignored # run everything
cargo test -- --ignored # run only the previously-ignored tests
```

You don't need `pnpm install` in test fixture directories.
Playwright's bundled Chromium is unavailable on musl, so the Vitest browser fixture is skipped there.

### Test Fixtures

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "vitest-browser-cache-fixture",
"private": true,
"type": "module"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[[e2e]]
name = "vitest_browser_caches_inputs_and_restores_outputs"
comment = """
Vitest browser mode runs in headless Chromium with explicit automatic input and output tracking. A source change must invalidate the cache, while an unchanged run must restore the browser command's generated output without rerunning Vitest. Playwright's bundled Chromium is unavailable on musl, so this browser-specific case is skipped only there.
"""
platform = "non-musl"
ignore = true
steps = [
{ argv = [
"vt",
"run",
"--cache",
"test",
], comment = "first browser run: cache miss writes dist/result.json" },
{ argv = [
"vtt",
"grep-file",
"dist/result.json",
"hello browser alpha",
], comment = "Vitest's JSON report contains the initial imported value" },
{ argv = [
"vtt",
"rm",
"dist/result.json",
], comment = "remove the generated output so restoration is observable" },
{ argv = [
"vt",
"run",
"--cache",
"test",
], comment = "unchanged inputs: cache hit restores dist/result.json" },
{ argv = [
"vtt",
"grep-file",
"dist/result.json",
"hello browser alpha",
], comment = "the automatic output archive restored Vitest's report" },
{ argv = [
"vtt",
"replace-file-content",
"src/greeting.js",
"hello browser alpha",
"hello browser bravo",
], comment = "modify a module loaded by the browser" },
{ argv = [
"vt",
"run",
"--cache",
"test",
], comment = "automatic input changed: cache miss reruns the browser test" },
{ argv = [
"vtt",
"grep-file",
"dist/result.json",
"hello browser bravo",
], comment = "the rerun's JSON report contains the modified imported value" },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# vitest_browser_caches_inputs_and_restores_outputs

Vitest browser mode runs in headless Chromium with explicit automatic input and output tracking. A source change must invalidate the cache, while an unchanged run must restore the browser command's generated output without rerunning Vitest. Playwright's bundled Chromium is unavailable on musl, so this browser-specific case is skipped only there.

## `vt run --cache test`

first browser run: cache miss writes dist/result.json

```
$ vitest run

RUN v4.1.10 <workspace>

✓ chromium src/greeting.test.js (1 test) <duration>
JSON report written to <workspace>/dist/result.json
```

## `vtt grep-file dist/result.json 'hello browser alpha'`

Vitest's JSON report contains the initial imported value

```
dist/result.json: found "hello browser alpha"
```

## `vtt rm dist/result.json`

remove the generated output so restoration is observable

```
```

## `vt run --cache test`

unchanged inputs: cache hit restores dist/result.json

```
$ vitest run ◉ cache hit, replaying

RUN v4.1.10 <workspace>

✓ chromium src/greeting.test.js (1 test) <duration>
JSON report written to <workspace>/dist/result.json

---
vt run: cache hit.
```

## `vtt grep-file dist/result.json 'hello browser alpha'`

the automatic output archive restored Vitest's report

```
dist/result.json: found "hello browser alpha"
```

## `vtt replace-file-content src/greeting.js 'hello browser alpha' 'hello browser bravo'`

modify a module loaded by the browser

```
```

## `vt run --cache test`

automatic input changed: cache miss reruns the browser test

```
$ vitest run ○ cache miss: 'src/greeting.js' modified, executing

RUN v4.1.10 <workspace>

✓ chromium src/greeting.test.js (1 test) <duration>
JSON report written to <workspace>/dist/result.json
```

## `vtt grep-file dist/result.json 'hello browser bravo'`

the rerun's JSON report contains the modified imported value

```
dist/result.json: found "hello browser bravo"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const greeting = 'hello browser alpha';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect, test } from 'vitest';
import { page, server } from 'vitest/browser';
import { greeting } from './greeting.js';

test(greeting, async () => {
document.body.innerHTML = `<h1>${greeting}</h1>`;

await expect.element(page.getByRole('heading')).toHaveTextContent(greeting);
expect(server.browser).toBe('chromium');
expect(server.provider).toBe('playwright');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"tasks": {
"test": {
"command": "vitest run",
"cache": true,
"input": [{ "auto": true }],
// TODO: Add @voidzero-dev/vite-task-client and have Vitest report this path as ignored.
"output": [{ "auto": true }, "!node_modules/.vite/vitest/**"],
"untrackedEnv": ["PLAYWRIGHT_HOST_PLATFORM_OVERRIDE"]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { playwright } from '@vitest/browser-playwright';
import { defineConfig } from 'vitest/config';
import { DefaultReporter } from 'vitest/node';

class NoTestSummaryReporter extends DefaultReporter {
reportTestSummary() {}
}

export default defineConfig({
test: {
reporters: [new NoTestSummaryReporter({ summary: false }), 'json'],
outputFile: { json: 'dist/result.json' },
browser: {
enabled: true,
headless: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
});
20 changes: 16 additions & 4 deletions crates/vite_task_bin/tests/e2e_snapshots/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ use vite_str::Str;
use vite_workspace::find_workspace_root;

/// Timeout for each step in e2e tests.
/// Windows CI needs a longer timeout due to Git Bash startup overhead and slower I/O.
/// Windows CI and `x86_64` macOS CI need longer for process and browser startup.
const STEP_TIMEOUT: Duration =
if cfg!(windows) { Duration::from_secs(60) } else { Duration::from_secs(20) };
if cfg!(any(windows, all(target_os = "macos", target_arch = "x86_64"))) {
Duration::from_secs(60)
} else {
Duration::from_secs(20)
};

/// Screen size for the PTY terminal. Large enough to avoid line wrapping.
const SCREEN_SIZE: ScreenSize = ScreenSize { rows: 500, cols: 500 };
Expand Down Expand Up @@ -226,8 +230,8 @@ struct E2e {
#[serde(default)]
pub cwd: RelativePathBuf,
pub steps: Vec<Step>,
/// Optional platform filter: "unix", "linux", "linux-gnu", "macos", or
/// "windows". If set, test only runs on that platform.
/// Optional platform filter: "unix", "linux", "linux-gnu", "non-musl",
/// "macos", or "windows". If set, test only runs on that platform.
#[serde(default)]
pub platform: Option<Str>,
/// When true, the generated libtest-mimic trial is marked `#[ignore]`
Expand Down Expand Up @@ -444,6 +448,11 @@ fn run_case(
// by vt100's `Screen::contents()` when the snapshot is rendered,
// so this does not introduce colour-noise into existing snapshots.
cmd.env("TERM", "xterm-256color");
// The macOS x64 CI shard needs Playwright to select an x64 browser
// while running under Rosetta on Apple Silicon.
if let Some(value) = env::var_os("PLAYWRIGHT_HOST_PLATFORM_OVERRIDE") {
cmd.env("PLAYWRIGHT_HOST_PLATFORM_OVERRIDE", value);
}
// On Windows, ensure common executable extensions are included in PATHEXT for command resolution in subprocesses.
if cfg!(windows) {
cmd.env("PATHEXT", ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC");
Expand Down Expand Up @@ -662,6 +671,9 @@ fn main() {
// spawned children, which breaks fixtures that
// depend on interposer ordering.
"linux-gnu" => cfg!(target_os = "linux") && !cfg!(target_env = "musl"),
// Playwright's bundled browser binaries do not
// support musl targets.
"non-musl" => !cfg!(target_env = "musl"),
other => panic!("Unknown platform '{}' in test '{}'", other, e2e.name),
};
if !should_run {
Expand Down
7 changes: 7 additions & 0 deletions crates/vite_task_bin/tests/e2e_snapshots/redact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String {
use cow_utils::CowUtils as _;
workspace_root_stripped.cow_replace('\\', r"\\").into_owned()
};
let workspace_root_forward_slashes = {
use cow_utils::CowUtils as _;
workspace_root_stripped.cow_replace('\\', "/").into_owned()
};

let mut redactions: Vec<(&str, &str)> = vec![
(workspace_root, "<workspace>"),
Expand All @@ -61,6 +65,9 @@ pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String {
{
redactions.insert(1, (&workspace_root_stripped_escaped, "<workspace>"));
}
if workspace_root_forward_slashes != workspace_root_stripped {
redactions.push((&workspace_root_forward_slashes, "<workspace>"));
}

redact_string(&mut output, &redactions);

Expand Down
6 changes: 5 additions & 1 deletion packages/tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
"private": true,
"type": "module",
"dependencies": {
"@playwright/browser-chromium": "catalog:",
"@vitest/browser-playwright": "catalog:",
"@voidzero-dev/vite-task-client": "workspace:*",
"cross-env": "catalog:",
"oxfmt": "catalog:",
"oxlint": "catalog:",
"oxlint-tsgolint": "catalog:",
"vite": "catalog:"
"playwright": "catalog:",
"vite": "catalog:",
"vitest": "catalog:"
}
}
Loading
Loading