Files
civitai__civitai/tests
Zachary Lowden 08f81f0bac test(preview): fix whatIfFromGraph smoke test against tRPC request batching (#3541)
* test(preview): fix whatIfFromGraph smoke test against tRPC request batching

The `whatIfFromGraph fires on /generate` preview smoke test has failed 100% of
the time on every PR. It is NOT a cold pod, a DB artifact, or a product defect:
driving a live preview shows the request fires ~1.3s after navigation and
returns HTTP 200 with `cost.total = 8`.

Two test-side defects, both introduced by `trpcBatching` (#2946) ramping on:

1. URL predicate. `httpBatchStreamLink` coalesces concurrent queries into one
   request whose path is the COMMA-JOINED procedure list, and whatIf is last:
     /api/trpc/content.get,challenge.getInfinite,...,orchestrator.whatIfFromGraph
   That does not contain `/api/trpc/orchestrator.whatIfFromGraph`, so
   `waitForResponse` never matched and timed out on a request that succeeded.
   Now matched by procedure name within the path list (batched or not).

2. Body shape + capture. The client sends `trpc-accept: application/jsonl`, so
   the response is newline-delimited chunks — `response.json()` throws, and the
   payload sits at a different depth than the old walk expected. Parse raw text,
   handling all three wire shapes, and locate the payload structurally (numeric
   `cost.total` + boolean `ready`, the whatIf contract) so a sibling procedure in
   the batch cannot produce a false pass.

   The app also reads the full stream and then abandons the idle reader, which
   Chromium reports as `net::ERR_ABORTED` and evicts — making a later
   `response.text()` fail with "No data found for resource" on 4 of 8 loads.
   The body is now buffered by a route handler, removing that race.

Verified against the live preview for PR #3535: red at origin/main (45s
`waitForResponse` timeout, the exact CI signature), green after, 12/12 runs
(11/12 without the internal retry), ~2s to the assertion. Mutation-checked:
disabling the jsonl branch fails with this guard's own error
("cost.total parsed from whatIfFromGraph response" -> null).

The 45s budget is unchanged and the test still guards the pre-spend cost quote.

* fix(test): narrow capturedBody honestly instead of casting through null

CI Typecheck failed:
  tests/preview-generation.spec.ts(177,24): error TS2352: Conversion of type
  'null' to type 'string' may be a mistake

`capturedBody` is declared `string | null`, but it is assigned from inside the
`page.route` closure, which TS control-flow analysis cannot see. After the
per-attempt reset to null, TS narrows it to `null` at the return, making
`as string` an illegal null->string conversion.

Replaced the `expect(...).not.toBeNull()` + cast with an explicit null check.
That narrows the type honestly, removes the cast entirely, and fails with a
message distinguishing the two halves: the response was observed, but the
route handler did not capture its body.

Verified: tsc error count 22 at origin/main and 22 here in the same
environment (delta 0), with zero errors mentioning this spec. The 22 are
pre-existing local artifacts (no prisma generate on NixOS, missing submodule).

Why this was not caught before pushing: the fix was verified by RUNNING the
Playwright spec 12 times, and Playwright transpiles without full type
checking. `tests/` is in the tsconfig include set, so CI checks it — a green
Playwright run is not a typecheck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: audit <alexhurwitz.dev@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 13:58:00 -05:00
..
2025-08-12 13:20:21 -04:00
2025-05-23 14:00:07 -04:00

Video: https://discord.com/channels/955572167662260295/1062092338698145812/1338591521401733192

# Playwright Testing

### Goal
E2E testing for the main app.
Catch potential issues when changing code, or evaluate edge cases.

### Anti-Goal
For this to be a frustrating pain-in-the-ass time vampire.
It's better to have 1 decent test than try to do 10 perfect ones and give up because it's too time consuming.

### What to Do

- Write tests for common user flows (fill a form, click a button, get a result)
- Handle what-if scenarios (errors, browsers, etc)
- A good rule of thumb is: if it's been reported as a bug, we should have a test in place for it (and things like it)

### What Not to Do

- Write "1==1" tests (dopamine hit, but pointless)
- Mandate coverage percentages (leads to annoyance and features not being done)

---

### How

Testing is intended to work on local development (docker) for consistency with users/data and easy tear down.

1) Run local services (`make init` or devcontainers)
2) Create a file in the `tests/` directory, or use an existing one. Doesn't really matter. Open to directory structure, so something like `tests/generator/gen-queue.spec.ts` would be reasonable.
3) Start writing tests.
    (a) can be done by hand if you know what you're looking to do
    (b) easier approach: `npm run test:gen -- --load-storage tests/auth/{user}.json --viewport-size 1920,1080 http://localhost:3000/{url}`
        - This allows you to create tests by interacting with the page and picking locators
    (c) we'll need better locators, especially for icons. add `data-testid=` to the places you need them (they'll be stripped from production)
    (d) use the various authed users to test different scenarios (mod, full access, muted, etc)
    (e) feel free to mock responses from any of the APIs, but in general it's best to only do this for external services
4) Run with either `npm run test` or `npm run test:ui` to do it interactively with screenshots
5) If you need to reset the db after each test, you can either:
    (a) clean up the mutations as part of the test (delete an object you just made)
    (b) `make boostrap-db` to reset the whole database back to normal
6) We'll eventually set up the github action to run this before a deploy

### Test Failures

There are 4 types of test failures:

1) A bad test (always fail)
    - these might have bad selectors or inaccurate logic
    - **solution**: fix them
2) A flaky test (sometimes fail)
    - frustrating tests which seem to pass most of the time, but not always
        - this is usually a result of race conditions, mismatched timing, or not properly awaiting events like animations
    - **solution**: narrow down which part of the test fails, and catch the flaky issue
3) Intended code change
    - we might have changed the verbiage on a button, which makes certain locators no longer work
        - this is fine, although locators should try to be as agnostic as possible
    - alternatively, we might have simply changed the business logic
    - **solution**: in either case, simply update the test itself
4) Unintended code change
    - you've changed something in the app, and a test breaks due to the introduction of a bug
        - this is the major reason we have tests
    - **solution**: leave the tests alone, they're doing their job. fix the code.