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
2 changes: 1 addition & 1 deletion .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
with:
tier: personal
runner: self
has_ui: false
has_ui: true
design_doc: ""
secrets:
FEISHU_CI_WEBHOOK: ${{ secrets.FEISHU_CI_WEBHOOK }}
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/ui-browser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Browser regression

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
workflow_dispatch:

permissions:
contents: read

jobs:
chromium-regression:
runs-on: [self-hosted, ci]
timeout-minutes: 15
steps:
- uses: actions/checkout@v6

- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: true
- name: Install Python dependencies
run: uv sync --frozen

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: |
package-lock.json
e2e/package-lock.json
- name: Install root Node dependencies
run: npm ci
- name: Install browser-test dependencies
working-directory: e2e
run: npm ci
- name: Install canonical Chromium browser
working-directory: e2e
run: npx playwright install chromium

- name: Run existing Vitest web contracts
run: npm run test:web
- name: Run blocking Chromium regression
run: npm run test:browser
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
过程中请使用中文和我沟通,但 console 里请优先使用英文。

ui-evidence-exempt: 2026-10-08 PWRS_REPORTER_TOKEN 缺失且自建 runner 的 PWRS 访问与实际发布链路尚未验证 (#199)
# Repository Guidelines

## Project Structure & Module Organization
Expand Down
11 changes: 11 additions & 0 deletions docs/sessions/261008-local-upload/progress/U-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# U 进度

## 里程碑 1:真实 Chromium 阻断入口(implementing)

已接入 `npm run test:browser`,用临时 SQLite 种子和动态端口驱动 Chromium 对提交、历史、公开阅读/导出及其他实际页面做行为/烟雾检查;9 条用例本地全绿。真实页面缺少 `sw.js` 时浏览器报告 404 console error,已补 test-server 路由并保留 console/request 失败观察;无产品代码改动。

决策:阻断层独立于 Vitest 和录屏层;`retries=0`、单 spec、`fullyParallel=false`,各用例 beforeEach 重新种子。下一步:入口 commit `665e84eb` 已推送并开 draft PR #200;继续接入阻断 CI、登记 has_ui caller 与录屏豁免文档。

## 里程碑 2:CI、覆盖文档与录屏例外(implementing)

新增 PR 阻断 workflow,保留现有 gate caller 且改 `has_ui: true`;实际查询 `zlxlabs/gate` 的 `v2` workflow_call schema 确认布尔输入存在。PWRS secret 名称检查结果为 absent,因此未创建录屏 workflow,按 90 天约定写入豁免并开本仓 issue #199;上游台账 #1393 仍 open。Chromium 9/9、Vitest 170/170 通过;将控件选择器临时改为不存在值会得到 `expect(locator).toBeVisible()` AssertionError,将 test-only URL 改为不存在路径会由 HTTP 404/console 404 观察器触发 `toEqual` AssertionError,恢复后重新全绿。PR #200 的真实 Chromium workflow `chromium-regression` 已 SUCCESS,gate quality 也 SUCCESS;PR 仍为 draft,`gate / primary` 按预期 SKIPPED,不能声称模型主审通过。唯一下一步:由 lead 做独立 review,并在其授权下标 ready 后等待完整 gate;执行器不自行 ready/merge。
25 changes: 25 additions & 0 deletions docs/testing/local-upload-browser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 本仓真实浏览器回归

阻断入口:`npm run test:browser`。使用本地 Chromium、`e2e/` 锁定依赖、动态回环端口和测试 server 每次新建的临时 SQLite 数据库;每个 case 调用 `POST /__e2e__/reset` 独立重种。测试 server 仅在 `tests/browser_server.py`,不接生产配置、数据库或服务,不启动 ASR、LLM、通知服务。提交接口的 HTTP 响应为受控 fixture,因此本套测试验证现有页面与交互,不代表后台上传实现通过。

## 页面与覆盖

| 实际页面 / 路由 | 级别 | 阻断 spec 与可见断言 |
|---|---|---|
| `/`(`views.py` 的生产 HTML 常量) | 次要 smoke | `home page smoke`:标题及提交/历史链接 |
| `/add_task_by_web`(`src/web/static/index.html`) | 主路径 | `submission page sends...`:填令牌与 URL、点击提交、核对浏览器实际 POST payload 及成功状态;另有表单 smoke |
| `/static/history.html`(真实静态页面) | 主路径 | `history query renders...`:真实查询渲染 SQLite 种子、打开阅读页、已读状态变化;另有控件 smoke |
| `/view/{view_token}` 成功阅读(`src/web/templates/transcript.html`) | 主路径 | `public reading page...`:正文可见、展开导出控件、点击 TXT/HTML 导出并核对结果 |
| `/view/{view_token}` 处理中(`processing.html`) | 次要 smoke | `processing view smoke`:处理中状态与刷新控件 |
| `/view/{view_token}` 失败(`error.html`) | 次要 smoke | `failed view smoke`:错误标题和消息 |
| `/view/{view_token}` 文件清理(`cleaned.html`) | 次要 smoke | `cleaned view smoke`:清理状态和恢复指引 |

`/view/{token}?raw=...` 与 `?page=...` 是阅读页导出目的地,主路径测试通过真实链接点击断言。`/robots.txt`、`/sitemap.xml`、`/sw.js`、manifest 与 API 是资源/接口,不是额外页面;profile 路由是 API JSON,不是 UI。浏览器测试收集 console error、page error、request failed 及 HTTP 4xx/5xx,任一均使 case 失败。

## 运行及 CI

首次运行:`uv sync`、根目录 `npm ci`、`npm --prefix e2e ci`,并安装 Chromium:`cd e2e && npx playwright install chromium`。之后 `npm run test:browser`;保留的 Vitest 契约层单独以 `npm run test:web` 运行。缺 Chromium、服务无法启动或断言不符均非零;无 skip/retry。单一 spec、`fullyParallel: false`、`workers: 1` 避免共享 fixture 并发污染。

`.github/workflows/ui-browser.yml` 对所有 PR `opened`、`synchronize`、`reopened`、`ready_for_review`、`converted_to_draft` 事件在 `[self-hosted, ci]` 执行 Chromium 与 Vitest;也可 `workflow_dispatch`。本仓没有 push workflow。`.github/workflows/gate.yml` 的 caller 已设置 `has_ui: true`;引用的 `zlxlabs/gate/.github/workflows/gate-v2.yml@v2` 在实际 tag 的 `workflow_call.inputs` 声明 `has_ui: boolean`(tag `v2` 当前解析为 commit `805856b9c6a501145521e8cae06000dc669af8c6`,已通过 GitHub API 读取实际 workflow_call schema 确认)。gate-hub 台账仍待 #1393,不能据本仓 caller 推断登记已生效。

录屏证据不在本阻断 workflow。因无 `PWRS_REPORTER_TOKEN` 且 runner 发布通路未经验证,未接假 workflow;`AGENTS.md` 中有效期 90 天的 `ui-evidence-exempt:` 对应本仓接入待办 #199。该豁免只针对录屏,不豁免真实浏览器行为门禁。
96 changes: 96 additions & 0 deletions e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "video-transcript-api-browser-tests",
"private": true,
"version": "0.1.0",
"description": "Blocking Chromium regression tests for the existing VideoTranscriptAPI web pages",
"scripts": {
"test": "node run-browser-tests.mjs"
},
"devDependencies": {
"@playwright/test": "1.62.0",
"@types/node": "22.20.1"
}
}
31 changes: 31 additions & 0 deletions e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineConfig, devices } from '@playwright/test';

const port = process.env.VTA_BROWSER_PORT;
if (!port) throw new Error('VTA_BROWSER_PORT must be assigned by run-browser-tests.mjs');
const baseURL = `http://127.0.0.1:${port}`;

export default defineConfig({
testDir: './tests',
testMatch: '**/*.spec.ts',
outputDir: './node_modules/.cache/playwright-test-results',
fullyParallel: false,
forbidOnly: true,
retries: 0,
workers: 1,
reporter: 'list',
use: {
baseURL,
browserName: 'chromium',
...devices['Desktop Chrome'],
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
webServer: {
command: `uv run python ../tests/browser_server.py --port ${port}`,
cwd: process.cwd(),
url: `${baseURL}/__e2e__/ready`,
reuseExistingServer: false,
timeout: 30_000,
stdout: 'pipe',
stderr: 'pipe',
},
});
8 changes: 8 additions & 0 deletions e2e/reset-backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { APIRequestContext } from '@playwright/test';

export async function resetBackend(request: APIRequestContext, baseURL: string): Promise<void> {
const response = await request.post(`${baseURL}/__e2e__/reset`);
if (!response.ok()) {
throw new Error(`POST /__e2e__/reset failed with HTTP ${response.status()}`);
}
}
18 changes: 18 additions & 0 deletions e2e/run-browser-tests.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createServer } from 'node:net';
import { spawn } from 'node:child_process';

const portServer = createServer();
portServer.listen(0, '127.0.0.1');
await new Promise((resolve) => portServer.once('listening', resolve));
const address = portServer.address();
if (!address || typeof address === 'string') throw new Error('Could not allocate browser-test port');
await new Promise((resolve, reject) => portServer.close((error) => error ? reject(error) : resolve()));

const playwright = spawn('npx', ['playwright', 'test'], {
env: { ...process.env, VTA_BROWSER_PORT: String(address.port) },
stdio: 'inherit',
});
playwright.on('exit', (code, signal) => {
if (signal) process.kill(process.pid, signal);
else process.exitCode = code ?? 1;
});
Loading
Loading