Skip to content

Add real-model browser E2E nightly tests with reusable scenarios #204

Closed

Description

@hsliuustc0106

Goal

Add a repeatable nightly E2E suite that exercises the real user flow:

Browser → FlyBot Web → DSH agent loop → real DeepSeek API → real tools → delivered files visible in the browser.

The existing .github/workflows/nightly.yml is a compatibility matrix. It does not cover this real-model user journey. Related: #30.

Keep scenario inputs and result assertions independent of the application so that Orbit can run the same scenarios through its own UI adapter. Orbit's current nightly implementation has not been inspected; integration with it remains to be validated.

Initial scenarios

Use one fresh, isolated test home and workspace per run. Drive task submission, attachment upload, follow-up messages, reload, and artifact preview through the browser. Read-only API polling and independent filesystem checks may observe completion and verify results; they must not perform the user's task.

Fixture:

order_id,region,status,amount
A001,华东,paid,100
A002,华北,paid,200
A003,华东,pending,300
A004,华东,paid,40
A005,华北,paid,60
  1. Upload and process a file. Upload the CSV and ask for paid orders as summary.json, with orders (full order details), count, and numeric total. Independently assert IDs A001/A002/A004/A005, count 4, total 400, and every retained row's fields.
  2. Correct the task in the same conversation. Ask to include only paid orders in 华东, retain the details/schema, save summary-east.json, and keep the original unchanged. Assert IDs A001/A004, count 2, total 140, and byte-for-byte preservation of the original.
  3. Restore the conversation after reload. Reload the browser and verify that the conversation and both delivered artifact cards remain available and both files remain correct.
  4. Open delivered artifacts. Click each artifact's sidebar preview through the UI and compare the displayed JSON with the independently verified output file. A filename echoed in the user's prompt is not evidence of delivery.

Different valid tool sequences and reply wording are acceptable; verify results and constraints rather than exact model text.

Local pilot evidence

A single real-model browser pilot passed all four checks on 2026-10-17 (Asia/Shanghai) at commit 0052df09b98ed868d590aa79bbebdd85296ea9c4:

  • Runtime: Node 24.19.0; DSH 0.2.0-rc.2.
  • Route: deepseek-official/deepseek-flash; configured reasoning effort low, output cap 4096.
  • The test profile loaded FlyBot core and tree; tree delegation itself was not exercised.
  • Per-check elapsed times: 11.454 s, 8.911 s, 1.813 s, 0.551 s; sum 22.729 s, excluding setup, startup, debugging, and gaps between checks. These observations are not performance thresholds.
  • UI-reported session usage: approximately 116K tokens, 87% cache hit, 3 turns / 8 steps. This is not a billing calculation.
  • Screenshots, transcript, structured report, and output files were retained locally. The prototype runner is not committed yet.
  • Test service/browser were stopped; the repository was unchanged.
  • The report records per-check durations but does not yet record absolute run start/end timestamps.

This was one successful pilot, not evidence of nightly reliability.

Acceptance criteria

  • Add a documented local command for the live browser E2E suite, using the supported DSH CLI/profile interfaces and the pinned runtime; do not patch DSH.
  • Implement the four checks above, with fresh run IDs/data, isolated DSH_HOME/workspace, bounded waits, and cleanup on success, failure, or interruption. Never use the user's normal DSH home.
  • Separate shared fixtures/scenarios/result assertions from FlyBot UI operations. Document the adapter contract and the remaining work needed to run the same cases on Orbit.
  • Run the suite nightly and via manual dispatch, separately from the compatibility matrix and ordinary PR checks. Use the real model and tools; mock-based suites remain separate.
  • Load DEEPSEEK_API_KEY from CI secrets/environment. Missing credentials must be reported explicitly, never counted as a successful E2E run. Do not commit credentials or publish tokens/cookies in artifacts.
  • Record revision, runtime/model configuration, run ID, absolute start/end timestamps, per-check durations, outcomes, and available token/cache usage. Store machine-readable timestamps with an explicit timezone and show human-facing times in Asia/Shanghai.
  • Retain useful failure evidence: screenshots, conversation, sanitized service diagnostics, result files, and a structured report. Distinguish task/assertion failures, provider failures, and test infrastructure failures.
  • Set configurable time and usage/cost budgets; preserve failed attempts rather than allowing retries to hide regressions.
  • Validate repeated independent runs before claiming the suite is stable; npm run verify must remain green for the final implementation.

Likely locations

  • core/dev/: existing smoke.mjs (mock/API smoke), ui-shots.mjs (browser examples), and isolated bench conventions.
  • core/dev/harness/package.json: pinned DSH test runtime.
  • A dedicated live-E2E workflow under .github/workflows/.
  • Shared scenario/fixture modules and an application adapter interface, with their final location chosen during implementation.

Out of scope for the first version

Desktop, tree delegation, social/remote peers, multiple accounts, browser file downloads, and fault injection are not covered by the pilot or required for the initial suite. No production data or credentials should be used. Do not mark Orbit integration complete until its real entrypoint has been exercised.

Activity

  1. hsliuustc0106 commented on Oct 17, 2026

    @hsliuustc0106
    ContributorAuthor

    Review: readiness for an implementer

    Automated review, not a human sign-off. Basis: this issue text as of 2026-10-18, main at 4470c1e (the pilot's 0052df0 is an ancestor). I checked the claims against the repo instead of trusting them.

    Verdict: ready to schedule after four decisions. The scenario set and the result assertions are sound. Everything still undefined is about how the suite runs in CI, and each item changes the implementation shape — so decide them here, not in review.

    What checks out

    • Fixture math is right: paid = A001/A002/A004/A005 → 4 / 400; paid ∧ 华东 = A001/A004 → 2 / 140.
    • The pilot's tip 0052df0 is on main, and the runtime matches the pin in core/dev/harness/package.json (0.2.0-rc.2).
    • The pilot's route is real in that pinned DSH: provider deepseek-official plus model deepseek-flash exist in llm-deepseek-api-key / llm-deepseek, and DSH reads DEEPSEEK_API_KEY (and DEEPSEEK_BASE_URL) from the environment — so no DSH patch is needed, which satisfies the hard rule.
    • Separating this from nightly.yml (compat matrix) and from PR checks is the right call.

    Decide before implementation

    1. The launcher is coupled to a DSH checkout; CI installs DSH from npm.
    bench.sh starts pnpm -s dsh … --patch apps/web/tests/pin-browse-picker.overlay.yml from a deepseek-harness checkout. That overlay is a checkout-only test fixture: it pins the in-browser directory picker, because the native OS chooser cannot be driven by a browser automation page. Scenario 1 (attachment upload) depends on it. CI installs @deepseek-ai/dsh into core/dev/harness, where neither that path nor pnpm exists. Choose one and say so in this issue: (a) the workflow clones the harness at the pinned tag, or (b) the suite runs the npm-installed dsh with a FlyBot-owned overlay that disables and re-inserts the browse-picker rows.

    2. No browser driver is declared in this repo.
    lib.mjs resolves playwright-core out of a DSH checkout's pnpm store and says outright that "ds-bot installs no browser tooling of its own". CI has Chrome on ubuntu-latest, but no such store. Adding a pinned playwright-core (or driving Chrome over CDP) is a decision against that stated invariant, and it belongs here rather than in a review comment later.

    3. Secret provisioning and hygiene.
    At review time this repository has zero Actions secrets configured (GET /repos/.../actions/secrets → total_count: 0), so DEEPSEEK_API_KEY has to be added before the first scheduled run — say who does that. The repo is public, so Actions logs and artifacts are world-readable and a leak is permanent. The issue should require, concretely: the secret-bearing job runs on main only (if: github.ref == 'refs/heads/main', never pull_request), permissions: contents: read, web.log and diagnostics sanitized (?token= is written there — see bench.sh and the login regex in smoke.mjs), an explicit artifact allow-list, and no raw DSH_HOME upload. Also add a hard guard that refuses to start when DSH_HOME is unset or resolves to ~/.dsh; today that is prose only.

    4. Where the code lives — and therefore which gates the PR hits.
    core/dev/ is owned by ds-bot-core.md, so a live runner there forces a doc update and makes every edit flow through patch generation. Root dev/ is now an established home for dev-only harnesses (see below). State the choice; the two options have different review surfaces.

    Reuse the interop harness pattern (#201)

    main just gained dev/interop/ (#201): a development-only, runtime-agnostic harness with a no-network tripwire, fake clock, fresh tmpdir per run, and keyless tests wired into root npm run verify. This issue describes the same shape — shared scenarios and assertions, application-specific adapter, separate live runner — and should follow it rather than invent a second convention:

    • put fixture/scenario/assertion modules and their keyless unit tests (a fake adapter) in the same style, so ordinary PR CI covers the logic;
    • keep the live command out of npm test / npm run verify (the core glob is core/test/*.test.js);
    • put the adapter contract next to the existing interop contract (docs/interop/ already holds the OpenCode pilot contract), and say how the two relate — otherwise two "adapter contract" documents will coexist.

    Tighten these ambiguities

    • "Keep the original unchanged" names no file. Say exactly: the uploaded CSV in the run workspace, asserted by SHA-256 before/after, and summary.json likewise unmodified (scenario 4 compares it).
    • JSON comparisons: compare parsed JSON, not bytes — indentation, key order and \u escaping will differ. Keep byte-for-byte only for the input CSV. Assert count and total are numbers.
    • "Fresh run IDs/data" conflicts with a fixed fixture. Keep the CSV fixed (400/140 depend on it); freshness means new run ID, DSH_HOME and workspace per run.
    • Prove the run is real: assert the resolved route/base URL is not loopback and the key is not mock-key, and record the model id. That is what actually enforces "mock suites remain separate".
    • Budgets need default numbers to be reviewable: e.g. per-check ≤ 180 s, job timeout ≤ 20 min, per-run token cap ~250K (the pilot used ~116K), and zero retries inside a run.
    • Outcome/exit contract: pass | assertion_failed | provider_failed | infra_failed | budget_exceeded, mapped to exit codes, plus the report schema (run ID, ISO-8601 timestamps with offset, monotonic per-check durations, recorded config, usage).
    • Workflow conventions: separate file, actionlint-clean (the package job lints workflows in test.yml), concurrency with cancel-in-progress: false, a UTC cron slot distinct from compat's 16:00Z, commented in Asia/Shanghai, not added to tests-ok or branch protection, and optionally the same "window check" gate so no tokens are spent when main has no new commits.
    • Orbit is unverifiable right now: nothing in this repo references Orbit, so "remaining work to run the same cases on Orbit" cannot be checked. Link its entrypoint, or scope this to the contract + FlyBot adapter and file Orbit integration as a follow-up — same pattern as #198, marked not exercised.
    • Attach the pilot evidence: the runner is not committed and the report lacks absolute timestamps. Without the sanitized structured report/transcript attached here, the implementer re-derives the format this issue asks them to match.

    Suggested acceptance criteria additions

    • The suite runs in CI without a deepseek-harness checkout, or the workflow provisions one at the pinned tag — state which, and how the directory-picker pin is applied.
    • The browser driver is a pinned, repo-declared dependency, with its Chrome source documented.
    • The run fails closed when the resolved provider is not the real endpoint, or when DEEPSEEK_API_KEY is missing or a placeholder — never reported as a pass.
    • DEEPSEEK_API_KEY is provisioned as a repository secret; the secret-bearing job runs on main only, and artifacts pass a sanitizer that rejects token=, key material and cookies.
    • Shared scenarios/assertions are covered by keyless tests with a fake adapter, following the dev/interop pattern, so npm run verify stays green and meaningful.
    • "Stable" means N consecutive scheduled runs at one recorded configuration (e.g. 5); retries are never silent.

    No repository files were changed by this review.

  2. hsliuustc0106 commented on Oct 17, 2026

    @hsliuustc0106
    ContributorAuthor

    Decisions on the four blockers, per the readiness review (to be delivered by the implementation PR):

    1. Launcher/CI: the workflow provisions a deepseek-harness checkout at the pinned tag (derived from core/dev/harness/package.json → dsh-v<version>), exactly like the local bench, and the upstream apps/web/tests/pin-browse-picker.overlay.yml applies the directory-picker pin. No FlyBot-owned overlay; the suite never runs from the npm-installed CLI.
    2. Browser driver: playwright-core resolves from that same harness checkout through the existing dev/desktop-linux/lib.mjs helper — the repo still declares no browser tooling of its own. Chrome: CHROME_BIN, else google-chrome/chromium(-browser) on PATH (ubuntu-latest ships Chrome), else playwright's download; documented in the suite README.
    3. Secrets: DEEPSEEK_API_KEY is provisioned by the maintainer as a repository secret before the first scheduled run (this repo currently has zero secrets). The secret-bearing job runs on main only (schedule + dispatch guarded by github.ref == 'refs/heads/main'; never pull_request), permissions: contents: read, evidence passes a sanitizer that fails on token=, the secret's value or cookie material, and only an explicit allow-list is uploaded (never a raw DSH_HOME). The runner fails closed when the key is missing/mock-key/placeholder, when the resolved base URL is loopback, or when DSH_HOME is unset or resolves under ~/.dsh.
    4. Location: dev/live-e2e/, beside dev/interop/ (Add the offline cross-runtime collaboration harness #201): fixture/scenario/assertion modules with keyless fake-adapter tests wired into root npm run verify; the live runner (bench launcher, adapter, reporter) runs only via the documented command and the nightly/dispatch workflow. The adapter contract goes to docs/interop/ next to the OpenCode pilot contract, with a section relating the two. Orbit integration is filed as a follow-up marked not exercised.

    Tightenings adopted: CSV immutability asserted by SHA-256 (the uploaded copy in the run workspace, and summary.json across later scenarios); JSON compared parsed with count/total required to be numbers, byte-for-byte only for the CSV; freshness = new run id + fresh DSH_HOME/workspace with the fixture CSV fixed; the resolved route must be non-loopback and the key non-mock, recorded in the report; budgets default to per-check ≤ 180 s, job ≤ 20 min, ≤ 250K tokens/run, zero retries; outcome contract pass | assertion_failed | provider_failed | infra_failed | budget_exceeded mapped to exit codes 0–4 with a structured report (run id, ISO-8601 timestamps with offset, monotonic per-check durations, config, usage); workflow: own file, actionlint-clean, concurrency with cancel-in-progress: false, cron 30 16 * * * UTC (00:30 Asia/Shanghai, clear of compat's 16:00Z), window gate, never added to tests-ok.

    Maintainer asks (not blockers for the PR): attach the sanitized pilot report/transcript here, and add the DEEPSEEK_API_KEY secret before the first scheduled run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions