diff --git a/packages/runtime-tests/integration/pty/ci.test.ts b/packages/runtime-tests/integration/pty/ci.test.ts
new file mode 100644
index 0000000..1530132
--- /dev/null
+++ b/packages/runtime-tests/integration/pty/ci.test.ts
@@ -0,0 +1,34 @@
+import { test, expect } from "vite-plus/test";
+import { run } from "./helpers/run.ts";
+import stripAnsi from "strip-ansi";
+
+test.sequential("render only last frame in CI", async () => {
+ const output = await run("ci", { env: { CI: "true" }, columns: 0 });
+ const clean = stripAnsi(output).replaceAll("\r", "");
+ // Counter: 0 through Counter: 4 should be absent
+ for (let i = 0; i <= 4; i++) {
+ expect(clean).not.toContain(`Counter: ${i}`);
+ }
+ expect(clean).toContain("Counter: 5");
+});
+
+test.sequential("render all frames if CI=false", async () => {
+ const output = await run("ci", { env: { CI: "false" }, columns: 0 });
+ const clean = stripAnsi(output).replaceAll("\r", "");
+ for (let i = 0; i <= 5; i++) {
+ expect(clean).toContain(`Counter: ${i}`);
+ }
+});
+
+test.sequential("debug mode in CI", async () => {
+ const output = await run("ci-debug", { env: { CI: "true" }, columns: 0 });
+ const clean = stripAnsi(output).replaceAll("\r", "");
+ const count = clean.split("Hello").length - 1;
+ expect(count).toBe(2);
+});
+
+test.sequential("debug after exit", async () => {
+ const output = await run("ci-debug-after-exit", { env: { CI: "true" }, columns: 0 });
+ const clean = stripAnsi(output).replaceAll("\r", "");
+ expect(clean).toBe("HelloHello\nDONE");
+});
diff --git a/packages/runtime-tests/integration/pty/fixtures/ci-debug-after-exit.tsx b/packages/runtime-tests/integration/pty/fixtures/ci-debug-after-exit.tsx
new file mode 100644
index 0000000..4129b0b
--- /dev/null
+++ b/packages/runtime-tests/integration/pty/fixtures/ci-debug-after-exit.tsx
@@ -0,0 +1,11 @@
+import process from "node:process";
+import { createApp, Text } from "@vue-tui/runtime";
+import { defineComponent } from "vue";
+
+const App = defineComponent(() => () => Hello);
+
+const app = createApp(App);
+app.mount({ debug: true });
+app.unmount();
+await app.waitUntilExit();
+process.stdout.write("DONE");
diff --git a/packages/runtime-tests/integration/pty/fixtures/ci-debug.tsx b/packages/runtime-tests/integration/pty/fixtures/ci-debug.tsx
new file mode 100644
index 0000000..1ca8d75
--- /dev/null
+++ b/packages/runtime-tests/integration/pty/fixtures/ci-debug.tsx
@@ -0,0 +1,7 @@
+import { createApp, Text } from "@vue-tui/runtime";
+import { defineComponent } from "vue";
+
+const App = defineComponent(() => () => Hello);
+
+const app = createApp(App);
+app.mount({ debug: true });
diff --git a/packages/runtime-tests/integration/pty/fixtures/ci.tsx b/packages/runtime-tests/integration/pty/fixtures/ci.tsx
new file mode 100644
index 0000000..01dffa7
--- /dev/null
+++ b/packages/runtime-tests/integration/pty/fixtures/ci.tsx
@@ -0,0 +1,41 @@
+import { createApp, Static, Text } from "@vue-tui/runtime";
+import { defineComponent, onMounted, onScopeDispose, shallowRef } from "vue";
+
+const App = defineComponent(() => {
+ const items = shallowRef([]);
+ const counter = shallowRef(0);
+ let counterValue = 0;
+
+ onMounted(() => {
+ let timer: ReturnType;
+
+ const onTimeout = () => {
+ if (counterValue > 4) {
+ return;
+ }
+
+ counterValue += 1;
+ counter.value = counterValue;
+ items.value = [...items.value, `#${counterValue}`];
+ timer = setTimeout(onTimeout, 20);
+ };
+
+ timer = setTimeout(onTimeout, 20);
+
+ onScopeDispose(() => {
+ clearTimeout(timer);
+ });
+ });
+
+ return () => (
+ <>
+
+ {{ default: ({ item }: { item: string }) => {item} }}
+
+ Counter: {counter.value}
+ >
+ );
+});
+
+const app = createApp(App);
+app.mount();