跳到正文
Hacker News· ollipal·· 10 天前AI 评分40

ScreenCI:把教程视频变成 E2E 测试,由你的编码智能体编写

Show HN: ScreenCI – Tutorial videos as E2E tests, written by your coding agent

AI 导读

ScreenCI 可将一句提示词交给编码智能体,自动录制真实应用的旁白演示视频,并生成对应的 E2E 测试文件。它支持自动缩放跟随点击、拖拽等交互,可嵌入图片与 MP4 素材、生成自动更新的文档截图,旁白覆盖 79 种语言,兼容任意智能体且免费试用、无需账号。

正文 · AI 翻译

你应用的教程视频,来自一个提示词。

描述一个功能,把提示词粘贴到你的编码代理中。它会录制一段你真实应用的带旁白演示,可直接用于帮助中心、文档、社交媒体或产品本身。

适用于任何代理,例如

免费试用,无需账户。

真实渲染,绝非样机

返回内容

这里的每个片段都是真实应用的 ScreenCI 真实渲染,由一个提示词生成。镜头、旁白、字幕、品牌标识和语言都随每个教程一同生成。

镜头自动跟随流程

自动缩放追踪每一次点击、输入框和拖拽。

显示生成的端到端测试文件

镜头自动跟随流程

镜头会放大并平移掠过屏幕,跟随每一次交互:输入字段、分段控件、滑块拖拽、下拉选择。无需设置手动缩放点,除非你想明确指定。

import { autoZoom, hide, resetZoom, video } from 'screenci'

// Auto-zoom follows the action: the camera zooms and pans to whatever you touch,
// with no manual zoom points. The "Add a contact" form keeps its controls on two
// side-by-side cards (contact details left, plan and access right), so this one
// block zig-zags corner to corner and the camera makes big jumps between each
// interaction.

video('Auto-zoom', async ({ page }) => {
  await hide(async () => {
    await page.goto('https://demo.screenci.com/customers/new')
    await page.waitForLoadState('networkidle')
  })

  // A tight `amount` frames each control closely, so the camera makes a big pan
  // across the screen to follow each next interaction.
  await autoZoom(
    async () => {
      // Top-left: type into the work email field.
      await page
        .getByLabel('Work email')
        .pressSequentially('[email protected]')

      // Jump top-right: pick a plan from the segmented control.
      await page.getByRole('button', { name: 'Scale' }).click()

      // Jump back left: type into the full-name field.
      await page.getByLabel('Full name').pressSequentially('Emma Carter')

      // Jump right: drag the seat-count slider up to ~62 of 100. dragTo targets a
      // point along the slider root, and the camera follows the thumb across it.
      const thumb = page.locator('[data-slot="slider-thumb"]').first()
      const track = thumb.locator('xpath=ancestor::*[@data-slot="slider"][1]')
      const trackBox = (await track.boundingBox())!
      await thumb.dragTo(track, {
        targetPosition: {
          x: (trackBox.width * (62 - 1)) / (100 - 1),
          y: trackBox.height / 2,
        },
        moveDuration: 900,
        dragDuration: 1000,
        dragSteps: 32,
      })

      // Bottom corners: open a dropdown on each side and pick.
      await page.getByRole('combobox', { name: 'Account type' }).click()
      await page.getByRole('option', { name: 'Customer' }).click()
      await page.getByRole('combobox', { name: 'Region' }).click()
      await page.getByRole('option', { name: 'Asia Pacific' }).click()
    },
    { amount: 0.45 }
  )
  await resetZoom()
})

插入图片和视频

你自己的素材,按时间轴定时。

显示生成的端到端测试文件

插入图片和视频

将 PNG/SVG 图片和 MP4 片段作为定时文件叠加层放置。这与程序化叠加层不同:媒体是真实的素材文件,而非 React 或 HTML 叠加层。

import { hide, video } from 'screenci'

// Embed media files directly: images/SVGs and MP4 clips are assets on the
// timeline. Programmatic overlays are React/HTML components; these are files.
video.overlays({
  logo: { path: './assets/logo.png', fill: 'recording', duration: 2100 },
  preview: {
    path: './assets/1-sample-code.mp4',
    fill: 'recording',
    volume: 0,
    time: 3200,
  },
})('Embed media', async ({ page, overlays }) => {
  await hide(async () => {
    await page.goto('https://demo.screenci.com/analytics')
    await page.waitForLoadState('networkidle')
  })

  await overlays.logo()
  await page.waitForTimeout(900)
  await overlays.preview()
  await page.waitForTimeout(1400)
})

自动更新的文档截图

来自同一脚本的带边框静帧。

显示生成的端到端测试文件

自动更新的文档截图

用 screenshot() 将任意页面捕获为带边框的图像,配上代码叠加层,并在每次重新渲染时自动更新。

import { screenshot } from 'screenci'

const BACKGROUND_CSS =
  'radial-gradient(circle at top left, #dbeafe 0%, #eff6ff 28%, #f8fafc 58%, #e0f2fe 100%)'

// A framed still of a live page, dressed with two code-built overlays (a status
// pill and a lower-third caption). Because it comes from the same script as the
// videos, it re-renders and stays current on every run, no manual re-capture.
const shot = screenshot.overlays({
  // A React lower-third with props.
  caption: {
    path: './overlays/Callout.tsx',
    props: { title: 'Framed, branded, always current' },
    x: 300,
    y: 640,
    width: 640,
  },
  // A plain HTML status pill.
  statusPill: { path: './overlays/status-pill.html', x: 1000, y: 700, width: 420 },
})

screenshot.use({
  renderOptions: {
    screenshot: { aspectRatio: '16:9', margin: 96, mouse: { show: false } },
    recording: {
      roundness: 0.05,
      dropShadow: 'drop-shadow(0 22px 49px rgba(0,0,0,0.28))',
    },
    output: {
      background: { backgroundCss: BACKGROUND_CSS },
    },
  },
})

shot('Branded screenshot', async ({ page, overlays }) => {
  await page.goto('https://demo.screenci.com/sales/d3')
  await page.getByRole('heading', { name: 'Annual renewal and expansion' }).waitFor()

  // A still has no timeline: start each overlay and leave it open so it stays
  // baked into the captured image.
  await overlays.caption.start()
  await overlays.statusPill.start()
})

79 种语言旁白

这里的每一行都是不同的语言。

显示生成的端到端测试文件

79 种语言旁白

一次录制,通过逐条提示的语言覆盖,让每一行旁白用不同的语言说出。AI 语音和字幕,全部来自同一脚本。

import { hide, video } from 'screenci'

// One recording, every language: each cue overrides its own `language`, so a
// single render greets you in a different tongue on every line.
video.narration({
  hello: { cue: 'Hello from ScreenCI.', language: 'en' },
  spanish: { cue: '¡Compatible con el español!', language: 'es' },
  french: { cue: 'Compatible avec le français !', language: 'fr' },
  german: { cue: 'Unterstützt Deutsch!', language: 'de' },
  chinese: { cue: '支持中文!', language: 'cmn' },
  japanese: { cue: '日本語に対応しています!', language: 'ja' },
  hindi: { cue: 'हिंदी भी चलती है!', language: 'hi' },
})('Multilanguage narration', async ({ page, narration }) => {
  await hide(async () => {
    await page.goto('https://demo.screenci.com/')
    await page.waitForLoadState('networkidle')
  })

  await narration.hello()
  await narration.spanish()
  await narration.french()
  await narration.german()
  await narration.chinese()
  await narration.japanese()
  await narration.hindi()
})

每个市场都有自己的版本

一次录制,为每种语言本地化。

显示生成的端到端测试文件

每个市场都有自己的版本

录制一次页面,然后以你列出的每种语言渲染,每种都有自己的 AI 旁白和字幕。更新源文件,所有语言会同步重新渲染。

import { hide, video } from 'screenci'

// Localize the videos: record the /welcome page once, and ScreenCI renders it in
// every language you list, each with its own narration. `browserLocale` (on by
// default) sets the browser locale per render, so the page localizes itself and
// the narration matches.

// Fill the whole output frame (no gradient margin) so the localized content is
// full-bleed, and stays that way when embedded by the compose showcase. Keeps
// the shared marketing background + framing, but overrides size to 1 (full
// bleed) so this clip has no gradient margin.
video
  .renderOptions({
    output: {
      background: {
        backgroundCss:
          'radial-gradient(circle at top left, #dbeafe 0%, #eff6ff 28%, #f8fafc 58%, #e0f2fe 100%)',
      },
    },
    recording: { size: 1, roundness: 0.04, dropShadow: 1 },
  })
  // The target: the localized /welcome page, recorded in en/es/fi.
  .narration({
    en: { intro: 'Welcome to Northstar. Let’s set up your workspace.' },
    es: { intro: 'Bienvenido a Northstar. Vamos a configurar tu espacio.' },
    fi: { intro: 'Tervetuloa Northstariin. Otetaan työtila käyttöön.' },
  })('Localized welcome', async ({ page, narration }) => {
  await hide(async () => {
    await page.goto('https://demo.screenci.com/welcome')
    await page.waitForLoadState('networkidle')
  })

  await narration.intro()
  await page.waitForTimeout(800)
})

客户数据绝不进入录像

机密信息在画面捕获前已被遮蔽。

显示生成的端到端测试文件

客户数据绝不进入录像

用 redact() 遮蔽 API 密钥、令牌和个人身份信息。像素在画面捕获前已在页面中被遮蔽,因此机密信息绝不会进入上传的录像。这不是表面叠加:是真正的遮蔽。

import { hide, resetZoom, video, zoomTo } from 'screenci'

// True redaction: the mask is applied in the browser before any frame is
// captured, so the secret never enters the uploaded recording.
video('Redact secrets', async ({ page }) => {
  const visibleSecret = 'sk_live_51ExistingValue8f1c2a'
  const redactedSecret = 'sk_live_51P0J8vX2f7r9d3cH'

  await hide(async () => {
    await page.goto('https://demo.screenci.com/settings/company')
    await page.waitForLoadState('networkidle')
    await page.getByLabel('Tax ID').fill(visibleSecret)
  })

  const taxId = page.getByLabel('Tax ID')
  await zoomTo(taxId, { amount: 0.5, duration: 300 })
  await page.waitForTimeout(700)
  await taxId.fill(redactedSecret, {
    duration: 900,
    redact: true,
  })
  await resetZoom()
})

默认符合品牌风格

将每个片段框在你的品牌背景上。

显示生成的端到端测试文件

默认符合品牌风格

在网页编辑器中选择渲染背景,将每个录像框在带圆角和柔和阴影的品牌渐变背景上。

import { hide, video } from 'screenci'

// Frame every recording on your own brand background, with size, corner
// roundness, and shadow set once in code (or with the same controls in the
// editor).
video
  .renderOptions({
    output: {
      background: {
        backgroundCss:
          'linear-gradient(135deg, oklch(0.62 0.17 268), oklch(0.4 0.18 275))',
      },
    },
    recording: { size: 0.82, roundness: 0.06, dropShadow: 1 },
  })
  .overlays({
    caption: { path: './overlays/Callout.tsx', x: 72, y: 760, width: 640 },
  })('Styled backgrounds', async ({ page, overlays }) => {
  await hide(() => page.goto('https://demo.screenci.com/'))

  await overlays.caption.start()
  await page.getByRole('button', { name: 'Pipeline', exact: true }).click()
  await page.locator('[data-deal-card]', { hasText: 'Vega Logistics' }).click()
  await overlays.caption.end()
})

无聊的步骤快速略过

速度渐变跳过重复部分。

显示生成的端到端测试文件

无聊的步骤快速略过

用 speed() 快进重复的步骤,让每个演示都保持紧凑。

import { hide, resetZoom, speed, time, video, zoomTo } from 'screenci'

// Speed ramps two ways. Explicit `speed` or `time`, both work:
//   speed(n): a RELATIVE multiplier on playback. n > 1 fast-forwards, n < 1 slows
//             down. Best for compressing (or stretching) repetitive stretches.
//   time(ms): an ABSOLUTE target. The block is retimed to occupy exactly this
//             long, however long it really took, so you can pin one beat to a
//             precise duration.
// Shared marketing render styling (project-wide look): light radial-gradient
// background, framed recording tile with soft corners and a drop shadow.
video.renderOptions({
  output: {
    background: {
      backgroundCss:
        'radial-gradient(circle at top left, #dbeafe 0%, #eff6ff 28%, #f8fafc 58%, #e0f2fe 100%)',
    },
  },
  recording: { size: 0.9, roundness: 0.04, dropShadow: 1 },
})('Speed ramps', async ({ page }) => {
  await hide(async () => {
    await page.goto('https://demo.screenci.com/settings/company')
    await page.waitForLoadState('networkidle')
  })

  await zoomTo(page.locator('form').first())

  // speed(): fast-forward the repetitive fields into a quick montage (3x).
  await speed(3, async () => {
    await page
      .getByLabel('Legal name')
      .fill('Aperture Bio Inc', { editId: 'fill1' })
    await page.getByLabel('Tax ID').fill('47-2819001', { editId: 'fill11' })
    await page.getByLabel('Phone').fill('(512) 555-0142', { editId: 'fill10' })
    await page
      .getByLabel('Address')
      .fill('2200 Congress Ave', { editId: 'fill9' })
    await page.getByLabel('City').fill('Austin', { editId: 'fill2' })
    await page.getByLabel('Postal code').fill('78701', { editId: 'fill3' })
    await page.getByLabel('Country').fill('United States', { editId: 'fill8' })
  })

  // time(): slow one deliberate beat down to an exact three seconds, so the last
  // field types out in emphasized slow motion.
  await time(3000, async () => {
    await page
      .getByLabel('Billing email')
      .pressSequentially('[email protected]', {
        delay: 40,
        editId: 'pressSequentially2',
      })
  })

  await page.waitForTimeout(500)
  await resetZoom()
})

看起来像拍摄的,而非脚本化的

每次缩放和移动都有自然的运动模糊。

显示生成的端到端测试文件

看起来像拍摄的,而非脚本化的

缩放和光标移动在渲染时获得自然的运动模糊。

import { autoZoom, hide, resetZoom, video } from 'screenci'

// Own intent (strong zoom + cursor motion blur) plus the shared marketing
// render styling: light radial-gradient background and framed recording tile.
video.renderOptions({
  zoom: { motionBlur: 1 },
  mouse: { motionBlur: 1 },
  output: {
    background: {
      backgroundCss:
        'radial-gradient(circle at top left, #dbeafe 0%, #eff6ff 28%, #f8fafc 58%, #e0f2fe 100%)',
    },
  },
  recording: { size: 0.9, roundness: 0.04, dropShadow: 1 },
})('Motion blur', async ({ page }) => {
  await hide(async () => {
    await page.goto('https://demo.screenci.com/analytics')
    await page.waitForLoadState('networkidle')
  })

  // Every cursor move goes to something real: switch analytics views, glance at
  // the headline each view reveals, then pan back up to Overview. The tight zoom turns each hop into a fast pan, so both the camera
  // and the cursor show motion blur on moves that read as intentional.
  await autoZoom(
    async () => {
      const tab = (name: string) => page.getByRole('button', { name })
      const stat = (label: string) =>
        page.getByText(label, { exact: true }).first()

      await tab('Revenue').click()
      await page.waitForTimeout(400)
      await stat('ARR').hover()
      await page.waitForTimeout(600)

      await tab('Retention').click()
      await page.waitForTimeout(400)
      await stat('Net retention').hover()
      await page.waitForTimeout(600)

      await tab('Cohorts').click()
      await page.waitForTimeout(400)
      await page.getByRole('heading', { level: 3 }).first().hover()
      await page.waitForTimeout(600)

      await tab('Overview').click()
      await page.waitForTimeout(400)
    },
    { amount: 0.5, duration: 450, zoomOutDuration: 350, easing: 'ease-in-out' },
  )
  await page.waitForTimeout(500)
  await resetZoom({ zoomOutDuration: 350 })
})

你的设计系统,融入视频之中

标注和圆环,锚定到你的实时界面。

显示生成的端到端测试文件

你的设计系统,融入视频之中

用你自己的 React、Vue、Svelte 或 Solid 组件,或纯 HTML 和 CSS 构建的环形、标注、徽章和字幕。锚定在实时元素旁边,使用你应用的颜色进行样式设置,并在你的 .screenci.ts 文件中设定时间。

import { hide, video } from 'screenci'
import type { Locator } from '@playwright/test'

// Programmatic overlays: rings, callouts, and captions from code, anchored to
// the live UI and timed to the action. React and plain HTML side by side,
// over the demo app's deal pipeline.
video.overlays({
  // An accent ring sized to the live element.
  ring: (target: Locator) => ({ path: './overlays/ring.html', over: target, margin: 6 }),
  // Your React component, placed beside a live element.
  deal: (target: Locator) => ({
    path: './overlays/DealCallout.tsx',
    props: { company: 'Northwind Labs', value: '$61,200', probability: 0.75 },
    anchor: target, side: 'right',
  }),
  // A React lower-third and a plain-HTML chip at fixed spots.
  card: { path: './overlays/Callout.tsx', x: 72, y: 760, width: 640 },
  htmlChip: { path: './overlays/chip.html', x: 1440, y: 72, width: 400 },
})('Programmatic overlays', async ({ page, overlays }) => {
  await hide(() => page.goto('https://demo.screenci.com/pipeline'))

  const card = page
    .locator('[data-stage="Negotiation"] [data-deal-card]')
    .filter({ hasText: 'Northwind Labs' })
  const ring = overlays.ring(card)
  const deal = overlays.deal(card)
  await ring.start()
  await deal.start()
  await page.waitForTimeout(1800)
  await deal.end()
  await ring.end()

  await card.dragTo(page.locator('[data-stage="Closed won"]'))

  await overlays.card.start()
  await overlays.htmlChip.start()
  await page.waitForTimeout(2600)
  await overlays.htmlChip.end()
  await overlays.card.end()
})

竖屏适配社交,一个设置

更改宽高比,其他什么都不用动。

显示生成的 e2e 测试文件

竖屏适配社交,一个设置

将 aspectRatio 切换为 9:16,录制器会自动打开竖屏视口。无需裁剪,无需重新构图。

import { hide, video } from 'screenci'

// Vertical for social, one setting: the same demo app recorded at 9:16. The
// app's responsive layout takes over (the sidebar collapses into a top nav),
// and the render frames it for Reels, Shorts, and TikTok.
video
  .renderOptions({
    recording: { size: 0.96, roundness: 0.04 },
    output: {
      aspectRatio: '9:16',
      quality: '1080p',
      background: {
        backgroundCss:
          'radial-gradient(circle at top left, #dbeafe 0%, #eff6ff 28%, #f8fafc 58%, #e0f2fe 100%)',
      },
    },
  })
  .recordOptions({ aspectRatio: '9:16', quality: '720p' })(
  'Vertical video',
  async ({ page }) => {
    await hide(async () => {
      await page.goto('https://demo.screenci.com/')
      await page.waitForLoadState('networkidle')
    })

    await page.waitForTimeout(1200)
    await page.mouse.wheel(0, 700)
    await page.waitForTimeout(1500)
    await page.getByRole('button', { name: 'Inbox' }).click()
    await page.waitForTimeout(1500)
    await page.getByText('Onboarding timeline for the board').click()
    await page.waitForTimeout(1800)
  },
)

告诉旁白如何发声

逐行指定语气和表达方式。

显示生成的 e2e 测试文件

告诉旁白如何发声

通过一个通俗易懂的风格提示,逐条指定语气和表达方式,从平静的向导到十足的海盗。富有表现力的语音模型可解锁更多语言。所有套餐均可用。

import { video, voices } from 'screenci'

// Each cue carries its own style prompt, so the same voice adapts its
// delivery per line. Write the direction in plain English.
video.narration({
  hook: {
    cue: 'Every narration line can have its own speaking style.',
    voice: {
      name: voices.Nora,
      style: 'Warm and intriguing, drawing the viewer in.',
    },
  },
  pirate: {
    cue: 'Arr! Even a swashbuckling pirate can guide the tour, if that be the style ye prompt.',
    voice: {
      name: voices.Nora,
      style: 'A boisterous pirate captain, gravelly and full of swagger.',
    },
  },
  whisper: {
    cue: 'Or lean right in, because the best part is a little secret.',
    voice: {
      name: voices.Nora,
      style: 'A hushed, conspiratorial whisper, close to the mic.',
    },
  },
  outro: {
    cue: 'Write the style in plain English. The voice does the rest.',
    voice: {
      name: voices.Nora,
      style: 'Confident and conclusive, landing the final thought.',
    },
  },
})('Per-cue speaking style', async ({ page, narration }) => {
  await page.goto('https://demo.screenci.com/')

  await narration.hook()
  await narration.pirate()
  await narration.whisper()
  await narration.outro()
})

每个视频都包含

一个完整教程所需的一切

后期无需添加任何内容。语音、镜头、字幕、语言和链接都来自同一个提示。

自然的 AI 语音同步朗读每一步,镜头跟随动作,光标像手一样移动,字幕从第一条就开启。逐行选择语音及其语气,或带上你的 ElevenLabs API 密钥,用你自己声音的克隆来旁白。

旁白指南

脚本就是测试,所以每次录制都完全相同。当产品发生变化时,一键即可重新录制。

录制如何工作

文档插图和 README 图片是同一脚本的静帧,带有品牌标识、经过裁剪,并在 UI 变化时重新截取。

截图指南

支持和文档团队可以改写一行、更换语音或剪掉缓慢的部分。无需向工程团队提交工单;编辑会同步回脚本。

通过提示制作视频

一个源视频,以你列出的每种语言进行旁白和字幕,每种语言都在本地化页面上录制。

语言指南

在你的帮助中心或文档中嵌入一次 CDN 链接。它始终提供你批准的版本。

公开 URL 和嵌入

要求一个介绍卡片或标注,代理会用你自己的组件绘制它,因此与产品风格一致。

叠加层指南

你的源代码和浏览器会话保持原样。只有录制内容、其时间数据和视频脚本会被上传,并且 redact() 会在捕获前屏蔽敏感信息。录制器是开源的。

会上传什么

可选,面向工程团队

额外好处:每个教程同时也是一个端到端测试

这里没有任何强制要求:你只需在笔记本电脑上粘贴提示并在浏览器中编辑,就能获得完整产品。但每个视频背后的脚本恰好是一个普通的 Playwright 测试,所以如果你的工程师愿意,他们可以将其保留在代码仓库中,并让 CI 在每次发布时重新录制。

  • 存放在你的代码仓库中

    代理会将视频脚本提交到你的代码旁边。任何能读懂 Playwright 测试的人都可以审阅、进行版本管理并阅读它们。

  • 每次发布时重新录制

    添加到 CI 会在你自己的运行器上设置一条流水线,因此视频始终展示当前产品。

  • 流程中断会导致构建失败

    如果教程所走的流程停止工作,运行就会像任何其他测试一样失败,在过时的视频到达客户之前。

  • 不产生额外费用

    无论 CI 运行多少次,录制和预览都是免费的。你只需为导出付费。

常见问题

常见问题

其他所有内容都在文档中,而且提示本身会在执行之前解释它将要做什么。

阅读文档

这适用于什么样的视频?

教程:帮助中心演示、新手引导和文档截图,以及那些必须随产品变化而保持正确的视频。发布和上线短片也是同样的做法。参见通过提示词制作视频。

团队里由谁来粘贴提示词?

任何已经打开了 Claude Code 或 Codex 的人:CTO、工程师、产品负责人。这一次粘贴就完成了项目设置。之后,支持和文档人员可以在浏览器中编辑旁白和措辞,完全不需要 agent;一旦项目从 CI 录制,重新录制在应用中只需一键。

我需要会写代码吗?

不需要。你只需用一句话描述视频,编码 agent 会编写并运行其余的一切。底层上,视频就是一个脚本,但你永远不必打开它:旁白、语音、叠加层和语言都可以在网页编辑器中编辑。参见通过提示词制作视频。

旁白可以用我自己的声音吗?

可以。在浏览器中录制一段简短样本,或拖入一个音频或视频文件,ScreenCI 会通过你自己的 ElevenLabs API 密钥克隆它(在品牌页面添加一次,加密保存,之后不再显示)。克隆功能适用于所有套餐;来自你 ElevenLabs 库的语音在更高套餐中可用。参见ElevenLabs 语音。

哪些编码 agent 可以使用?

任何能运行 shell 命令的 agent 或工具链:Claude Code、Codex、Cursor、pi.dev、桌面 agent 等。提示词会告诉 agent 从哪里获取指令,一次性代码会将它连接到你的项目。agent 只会要求你在它打开的浏览器中登录你自己的产品,绝不会索要密码。

我需要会使用终端吗?

不需要。你把提示词粘贴到 agent 中,它会自行运行所有命令:安装 ScreenCI、编写脚本、录制,并在浏览器中打开结果。如果 agent 需要你提供任何信息,它会用通俗的语言询问。参见通过提示词制作视频。

我的应用或源代码会离开我的机器吗?

录制在本地针对你自己的应用运行,你应用的源代码永远不会离开你的机器。上传的是带有时间数据的屏幕录制,以及 screenci/ 文件夹的文本文件:配置和视频脚本,以便网页应用可以展示它们并交给编码 agent(设置 uploadSources: false 可让它们不被上传)。绝不会上传 env 文件、lockfile 或该文件夹之外的任何内容。发送内容的完整列表。CLI 是开源的,所以你可以自行验证。

当我的 UI 发生变化时会怎样?

点击重新录制,或让 CI 在下一次推送时执行,教程就会针对新 UI 重新渲染。如果流程本身出了问题,运行会像端到端测试一样失败,这样你就能在用户之前发现问题。参见仓库与 CI。

工程师可以把视频保留在仓库中吗?

可选。在仓库中运行提示词,agent 会在一个分支上提交脚本,添加到 CI 会设置一条流水线,在每次推送时重新录制它们。这样每个视频就变成了你的 CI 已经知道如何运行的 Playwright 端到端测试。参见仓库与 CI。

它是开源的吗?

CLI 和创作 API 在 GitHub 上以MIT 许可证发布,录制使用它们在本地运行。渲染(旁白、镜头、字幕、叠加层)、网页编辑器和托管是付费服务,并非开源。

如果 ScreenCI 消失了,我的视频会怎样?

这些脚本是你自己仓库中的 Playwright 测试,因此它们会继续作为测试运行。导出的是你下载的 MP4 文件(npx screenci export 会将它们写入 ./exports),所以你可以将它们托管在任何地方。只有托管的公开 URL 依赖于该服务。

费用是多少?

试用是免费的,且无需账号:三次带实时预览的试用录制。在所有套餐中,录制和实时预览都保持免费;只有渲染后的导出才计费,因此费用取决于你发布的视频,而不是你运行的次数。有关导出、语言和团队套餐,请查看定价。

为什么不直接使用 Playwright 内置的视频录制功能?

Playwright 的录制是原始的调试捕获。ScreenCI 将同一脚本变成成品产品视频:与每个步骤同步的 AI 旁白、自动缩放镜头、动画光标和打字效果、来自你自己 React/Vue/Svelte 组件的叠加层、字幕、翻译,以及一个永久的 CDN URL。看看脚本长什么样。

免费试用,无需账号

粘贴一条提示词。获取你的第一个教程。

三次带实时预览的试用录制,直接使用本页顶部的提示词即可。只有想保留它们时才需要注册。

适用于 Claude Code、Codex、Cursor、pi.dev,以及任何能运行 shell 命令的 agent。

要在团队中推广?预约 30 分钟通话

来源:Hacker News · screenci.com