Skip to content

[OPIK-8102] [QA] Proposed e2e specs for the LLM-judge Gemini thinking level (from #8158 exploration) - #8160

Draft
CometActions wants to merge 2 commits into
AndreiCautisanu/OPIK-8102/flash-lite-thinking-nonefrom
comet-qa-bot/OPIK-8102/e2e-llm-judge-thinking-level
Draft

[OPIK-8102] [QA] Proposed e2e specs for the LLM-judge Gemini thinking level (from #8158 exploration)#8160
CometActions wants to merge 2 commits into
AndreiCautisanu/OPIK-8102/flash-lite-thinking-nonefrom
comet-qa-bot/OPIK-8102/e2e-llm-judge-thinking-level

Conversation

@CometActions

@CometActions CometActions commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Where this came from

Exploratory testing of #8158 ([OPIK-8102] [FE] fix: add a "none" thinking level so Flash Lite models stay non-thinking) on that PR's own deployed environment, https://pr-8158.dev.comet.com (2.2.51-8158-merge-3187, OSS install, workspace default). A human worked the flows by hand there first; these two specs are the flows that came back verified and strong.

This PR targets AndreiCautisanu/OPIK-8102/flash-lite-thinking-none, not main — deliberately. Both specs assert behaviour that only exists in #8158: on main, Gemini 3.5 Flash Lite still defaults to minimal and its level list has no None at all, so the first spec fails on its very first assertion. The specs are written against fb5fc93, that PR's head. They should merge with #8158, not ahead of it.

What the specs assert

The failure mode this covers is silent. A judge rule that carries a thinking block it should not looks identical in the rules list, scores traces normally, and reads back correctly in the form — the only symptom is that every judge call is slower and costs more. So each assertion is made against the persisted evaluator over REST (GET /v1/private/automations/evaluators/{id}code.model.custom_parameters), with the dialog asserted separately as the thing a user actually reads.

Both are @t2-cuj @area:online-evaluation, in tests/online-evaluation/online-evaluation-thinking-level.spec.ts.

1. A Flash Lite judge saves no thinking block, keeps a level that was chosen, and clears it again on None

@cap:online-evaluation.create-llm-judge-rule · @cap:online-evaluation.edit-rule

Step Assertion
Create on Gemini 3.5 Flash Lite dialog preselects None; offered levels are exactly None, Minimal, Low, Medium, High
Submit saved evaluator's custom_parameters is absent — not {}, not {thinking: {}}
Reopen (kebab → Edit) hydrates None
Set Minimal, save persists exactly {thinking: {level: minimal}}
Reopen, save untouched still {thinking: {level: minimal}} — the new none default must not overwrite a level someone chose
Reopen, set back to None, save custom_parameters is gone again

That last row is the one #8158's head commit (make "none" clear a persisted thinking block, not just skip adding one) exists for, and it is the row the PR's own vitest suites cannot reach: it needs the full dialog → save → rehydrate → save round trip.

custom_parameters is compared as a whole object rather than probed for a key, so a leftover sibling under thinking fails rather than passing quietly.

2. A thinking-by-default Gemini model is untouched: Flash preselects Medium, is never offered None, and saves the level

@cap:online-evaluation.create-llm-judge-rule

The control. Gemini 3.5 Flash still preselects Medium, is not offered None, and still saves {thinking: {level: medium}}. Without it, a later "just stop sending thinking for Gemini" simplification would silently switch every Gemini judge to non-thinking and spec 1 would still pass.

Verification

Run against https://pr-8158.dev.comet.com — the same environment the exploration used — from tests_end_to_end/e2e/:

npx playwright test tests/online-evaluation/online-evaluation-thinking-level.spec.ts --reporter=list
Spec Result
Flash Lite: no block → Minimal → untouched resave → None clears it passed (3 consecutive runs, ~22s)
Flash control: Medium preselected, no None, level saved passed (3 consecutive runs, ~5s)

Also run:

  • python3 tests_end_to_end/coverage/tag_lint.py --taxonomy … --estate tests_end_to_end61 specs checked, 1 exempt, 0 problem(s).
  • The whole tests/online-evaluation/ directory, since this change touches a shared POM: 8 passed, 1 skipped, 1 failed. The skip is the LLM-judge smoke test (ensureModelAvailable — no ANTHROPIC_API_KEY/OPENAI_API_KEY/OPENROUTER_API_KEY on this runner). The failure is online-evaluation-python-metric-errors.spec.ts: the environment answers Python evaluation failed (HTTP '500'): 500 Internal Server Error: Failed to execute code where the spec expects the classified 400 Bad Request: Execution failed: the metric produced no output. Not from this branch — verified by checking out fb5fc93 clean and running that one spec, which fails identically. It looks like an environment/backend-side condition on pr-8158.dev.comet.com and is worth a separate look, but it is nothing to do with these specs.

tsc --noEmit on the estate reports exactly one error, core/backend/client.ts: Duplicate identifier 'deleteDashboard', which reproduces unchanged on main — it is not from this branch. (Separately, npx tsc with the repo's pinned typescript@7.0.2 fails on tsconfig.json before reaching any source: Option 'baseUrl' has been removed. Also pre-existing on main, and worth a fix in its own PR — the typecheck in the writing-e2e-tests skill's checklist cannot currently run as written.)

No completion is ever made and no LLM key is needed: the model picker lists a provider's models as soon as a key for it exists, and everything asserted here is decided before the judge would run. A dummy Gemini key is enough, which is what makes both specs deterministic and free.

Supporting changes (all additive)

  • fixtures/provider-key.fixture.ts — new geminiProviderKey fixture. Adopt-or-seed: a provider key is workspace-global and gemini cannot be namespaced per test the way providerKeys.createOauth namespaces a custom provider, so if the workspace already has a Gemini key the fixture uses it and leaves it alone; otherwise it seeds a dummy one and deletes it after use(), honouring shouldLeaveArtifacts. Because the resource is unnamespaceable, the spec declares test.describe.configure({ mode: 'serial' }) — verified: --workers=4 still reports "Running 2 tests using 1 worker".
  • core/provider-keys.tscreateProviderKey now also reports the created id from the Location header, and takes api_key / optional provider_name so a built-in provider can be seeded. Deliberately returns string | null rather than throwing on a missing header, so the existing token-auth callers (which clean up by provider_name) are behaviour-identical; the new fixture, which needs the id, is the one that complains.
  • core/backend/client.tsgetLlmJudgeModel(ruleId). Reads code.model raw, because the pinned SDK's evaluator shape has no custom_parameters. Nullable on purpose: absent and empty are different answers when the whole point is removal.
  • pom/online-evaluation.page.ts — new methods only; no existing method was changed, so no sibling spec's behaviour moves. Model picker scoped to its provider group, because Gemini and Vertex AI publish identical display labels (Gemini 3.5 Flash Lite is both gemini-3.5-flash-lite and vertex_ai/gemini-3.5-flash-lite) and an unscoped lookup would match two options — both the group and the option are asserted to toHaveCount(1) rather than taking .first().

One selector needs a reviewer's eye. The model-parameters gear (PromptModelConfigs) is an icon-only button with no accessible name and no data-testid — its "Model parameters" string lives in a tooltip, which contributes nothing to the accessible name — so the POM selects it by its icon (button:has(svg.lucide-settings2)), asserted to resolve to exactly one element inside the dialog. Per conventions.md the right fix is a data-testid on the trigger, in this same change. It is not here on purpose: these specs were verified against a prebuilt deployment of #8158, where a newly added attribute would not exist, so adding it would have meant shipping specs I could not run. Happy to add it (data-testid="model-parameters-trigger") and switch the POM over if you would rather have that than a verified run.

Taxonomy

online-evaluation.edit-rule flipped to covered: true, tier: t2-cuj — with a note scoping the claim to the dialog's model-settings pane, since the prompt, variable mapping, filters and sampling controls are still unexercised by an edit. The spec is added to the area's specs: list, and create-llm-judge-rule's note records the new Gemini-model creation path.

What was deliberately not written

One candidate of three was dropped: "Playground: switching to a Flash Lite model must not carry a thinking level onto the completion request." The exploration marked it weak, and it is the one flow that is currently wrong: coming from the playground's own default Gemini model, the Thinking level control stays at High and POST /v1/private/chat/completions sends custom_parameters.thinking.level: "high" on gemini-3.5-flash-lite. Two code paths write the config on a model change (PlaygroundPrompt.tsx): a provider change calls getDefaultConfigByProvider and applies the new "none" default, while a model change within Gemini calls updateProviderConfig, which only rewrites the level when the current one is not in the new model's list — and "high" is in Flash Lite's list both before and after this PR.

That is not a regression from #8158 (updateProviderConfig is not in the diff, and the pre-PR list also contained "high"), but it does mean an OSS user with only a Gemini key has no way to reach the path where the fix applies, so on the playground Flash Lite still runs with thinking at maximum. A spec written now would fail on merge. It is left unwritten pending the author's call on whether the playground's within-provider carry-over is in scope here — playground.configure-model-settings stays covered: false.

Please read before merging

Generated by the release QA side flow — written, run and committed by an automated agent. It is a draft on purpose and needs a human to judge whether each assertion is the right one before it is promoted. Base branch is #8158's head, not main.

Related: #8158

… level

Two Playwright specs for the behaviour #8158 introduces, written and run
against that PR's own environment.

The failure mode is silent — a judge rule that carries a thinking block it
should not looks identical in the list and scores traces normally, it is just
slower and more expensive — so every assertion is made against the persisted
evaluator over REST, with the form asserted separately as the thing the user
reads.

  - A Flash Lite judge preselects None, offers None/Minimal/Low/Medium/High,
    and saves no custom_parameters at all. Reopened it hydrates None; set to
    Minimal it persists {thinking: {level: minimal}}; reopened and resaved
    untouched it keeps that level; set back to None the block is REMOVED, not
    left behind. That last step is the one #8158's head commit fixes.
  - The control: Gemini 3.5 Flash still preselects Medium, is never offered
    None, and still saves {thinking: {level: medium}} — so a later "just send
    nothing for Gemini" simplification cannot pass unnoticed.

No completion is made: the model picker only needs a provider key to exist, so
a dummy Gemini key makes both models selectable and everything asserted is
decided before the judge would run.

Supporting changes, all additive:
  - geminiProviderKey fixture — adopt-or-seed, because a provider key is
    workspace-global and `gemini` cannot be namespaced per test the way a
    custom provider can. The spec runs serial for the same reason.
  - backendClient.getLlmJudgeModel — reads code.model.custom_parameters, which
    the pinned SDK's evaluator shape does not carry. Nullable on purpose:
    absent and empty are different answers when the point is removal.
  - OnlineEvaluationPage — model picker scoped to its provider group (Gemini
    and Vertex AI publish identical model labels), model-parameters popover,
    thinking-level read/list/set, and an edit-dialog opener.

Taxonomy: online-evaluation.edit-rule flipped to covered (t2-cuj), the spec
added to the area's list, and create-llm-judge-rule's note extended.

Generated by the release QA side flow; needs review before merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added tests Including test files, or tests related like configuration. typescript *.ts *.tsx labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📋 PR Linter Failed

Missing Section. The description is missing the ## Details section.


Missing Section. The description is missing the ## Change checklist section.


Missing Section. The description is missing the ## Issues section.


Missing Section. The description is missing the ## Testing section.


Missing Section. The description is missing the ## Documentation section.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

⏱️ pre-commit per-hook timing

Hook Description Result Duration
🌐 typecheck — frontend Whole-project tsc type check 30.75s
🌐 eslint — frontend Lint + autofix JS/TS 2.46s
Total (2 ran) 33.21s
⏭️ 42 skipped (no matching files changed)
Hook Description Result
🐍 trim trailing whitespace — python sdk Strip trailing whitespace ⏭️
🐍 fix end of files — python sdk Ensure files end in a newline ⏭️
🐍 ruff — python sdk Lint + autofix Python (ruff) ⏭️
🐍 ruff-format — python sdk Format Python code (ruff) ⏭️
🐍 mypy — python sdk Static type check ⏭️
🤖 trim trailing whitespace — optimizer Strip trailing whitespace ⏭️
🤖 fix end of files — optimizer Ensure files end in a newline ⏭️
🤖 check yaml — optimizer Validate YAML syntax ⏭️
🤖 check json — optimizer Validate JSON syntax ⏭️
🤖 check toml — optimizer Validate TOML syntax ⏭️
🤖 check for added large files — optimizer Block large files (>1MB) ⏭️
🔐 detect private key — optimizer Block committed private keys ⏭️
🤖 check for merge conflicts — optimizer Block merge-conflict markers ⏭️
🤖 check for case conflicts — optimizer Block case-only name clashes ⏭️
🤖 pyupgrade — optimizer Modernize Python syntax ⏭️
🤖 ruff — optimizer Lint + autofix Python (ruff) ⏭️
🤖 ruff-format — optimizer Format Python code (ruff) ⏭️
🤖 mypy — optimizer Static type check ⏭️
📓 nbstripout — optimizer notebooks Strip notebook output ⏭️
📝 markdownlint — optimizer Lint Markdown ⏭️
🔤 codespell — optimizer Fix common misspellings ⏭️
📊 radon cc — optimizer Cyclomatic-complexity gate ⏭️
📊 radon raw — optimizer Raw size metrics gate ⏭️
📊 xenon — optimizer Fail on complexity thresholds ⏭️
📊 lizard — optimizer Cyclomatic-complexity gate ⏭️
🧹 vulture — optimizer Find dead code ⏭️
🛡️ trim trailing whitespace — guardrails Strip trailing whitespace ⏭️
🛡️ fix end of files — guardrails Ensure files end in a newline ⏭️
🛡️ ruff — guardrails Lint + autofix Python (ruff) ⏭️
🛡️ ruff-format — guardrails Format Python code (ruff) ⏭️
🛡️ mypy — guardrails Static type check ⏭️
⚓ helm-docs Regenerate Helm chart README ⏭️
block non-public FE plugins Block non-public FE plugins ⏭️
☕ spotless — java backend Format Java code ⏭️
🧪 pre-commit wrapper smoke tests Self-test the wrapper scripts ⏭️
🧪 rebaseline script tests Self-test the changelog re-baseline script ⏭️
📘 eslint — typescript sdk Lint + autofix JS/TS ⏭️
📘 typecheck — typescript sdk Whole-project tsc type check ⏭️
⚙️ actionlint — github workflows Lint GitHub Actions workflows ⏭️
🐳 hadolint — dockerfiles Lint Dockerfiles ⏭️
🌈 zizmor — github workflows security Security-scan GitHub Actions workflows ⏭️
🛡️ semgrep — java backend sql Block SQL injection-prone string formatting ⏭️

Comment on lines 55 to +56
const body = (await response.json()) as { content: ProviderKeyRef[] };
return body.content.find((key) => key.provider_name === providerName) ?? null;
return body.content;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Malformed list responses cause opaque lookup crashes

listProviderKeys casts parsed JSON and returns body.content without checking that content is an array, so malformed successful responses reach .find and crash with an opaque TypeError — should we validate the response shape at the REST boundary before returning it?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`tests_end_to_end/e2e/core/provider-keys.ts` around lines 55-56, update
`listProviderKeys` so it does not blindly cast the parsed response and return
`body.content`. Validate that the response is an object with a `content` property that
is an array before returning it; otherwise throw a descriptive error indicating the list
response has an invalid shape, so `findProviderKeyByName` and
`findProviderKeyByProvider` never call `.find` on malformed data.

Comment on lines +264 to +265
await this.dialog.getByTestId('add-edit-rule-dialog-submit').click();
await this.dialog.waitFor({ state: 'hidden' });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Immediate REST assertions race edit PATCH

The submitRuleDialog spec waits only for the dialog to hide, so the edit callback can close it before updateMutate’s PATCH and onSettled invalidation finish, letting the next getLlmJudgeModel GET read stale custom_parameters and fail nondeterministically — should we await the mutation response or poll the REST value before asserting?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`tests_end_to_end/e2e/pom/online-evaluation.page.ts` around lines 264-265, update
`submitRuleDialog` so it does not return merely when the controlled dialog becomes
hidden; the edit handler starts the PATCH asynchronously and the next assertion can
observe stale `custom_parameters`. Coordinate the submit with the mutation's observable
network response, or poll the rule's REST value until the submitted parameters are
persisted before completing the helper.

Comment on lines +339 to +341
private get modelParametersTrigger(): Locator {
return this.dialog.locator('button:has(svg.lucide-settings2)');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use stable model-parameters test ID

PromptModelConfigs tests locate the trigger through Lucide's lucide-settings2 class, so a harmless icon change breaks them — should we add data-testid="model-parameters-trigger" and switch the locator to getByTestId, as .agents/skills/writing-e2e-tests/conventions.md recommends?

Severity

Want Baz to fix this for you? Activate Fixer You can also update your AI coding guidelines based on this comment by apply pr to [branch name]

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`tests_end_to_end/e2e/pom/online-evaluation.page.ts` around lines 339-341, update the
`modelParametersTrigger` locator to use `getByTestId('model-parameters-trigger')`
instead of the structural `lucide-settings2` selector. Add the matching kebab-case
`data-testid="model-parameters-trigger"` to the `PromptModelConfigs` gear button in its
frontend component, following the project’s E2E selector conventions, and preserve the
existing uniqueness assertion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit 9fdebd5 addressed this comment by adding data-testid="model-parameters-trigger" to the gear button and switching the POM locator to getByTestId('model-parameters-trigger') while retaining the uniqueness assertion.

* and a spec that cannot tell "no block" from "empty block" cannot assert
* that.
*/
customParameters: Record<string, unknown> | null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REST response type hides wire-format field

The handwritten raw REST type exposes backend custom_parameters as customParameters, while getLlmJudgeModel reads model.custom_parameters, so the type no longer matches the payload and callers rely on an undocumented mapping — should we rename the field and returned object to custom_parameters, or introduce a separately mapped camelCase DTO?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
tests_end_to_end/e2e/core/backend/client.ts around lines 203 and 1631-1637, update the
`LlmJudgeModelDetail` interface and `getLlmJudgeModel` method so the raw REST field
remains named `custom_parameters`, matching the backend payload and
`model.custom_parameters` access. Remove the undocumented camelCase transformation; if a
camelCase domain DTO is needed, define it separately and map to it explicitly.

Comment on lines +1633 to +1636
customParameters:
custom === null || custom === undefined
? null
: (custom as Record<string, unknown>),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepts malformed custom parameters

customParameters casts every non-null JSON value to Record<string, unknown>, so scalars and arrays reach object-only consumers and violate the accessor contract — should we require typeof custom === 'object' && !Array.isArray(custom) and throw a clear error for other non-null shapes?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
tests_end_to_end/e2e/core/backend/client.ts around lines 1633-1636, update
`getLlmJudgeModel` so `custom_parameters` is not blindly cast to `Record<string,
unknown>`. Preserve `null` for null or undefined, validate that other values are
non-null objects and not arrays, and throw a clear error identifying the invalid
`custom_parameters` shape before returning it.

Replace two fragile selectors in the new online-evaluation POM with stable
test ids, and document a cross-spec hazard on the workspace-global Gemini key.

- The model-parameters gear was addressed by `svg.lucide-settings2`, an
  internal lucide-react class derived from the icon name at build time and
  renamed without warning by a version bump. Add a `data-testid` to the
  trigger in PromptModelConfigs and select on that instead. The button is
  icon-only and its "Model parameters" string lives in a tooltip, so no
  role/label selector exists. Test-only hook, no behaviour change; same
  pattern as the add-edit-rule-dialog-submit id added by #6874.

- The LLM-judge model combobox was matched by a hardcoded provider-name
  regex, which stops matching for any provider outside the list. Scope it by
  the select-a-llm-model test id PromptModelSelect already ships.

- Note in the geminiProviderKey fixture that playground-providers.spec.ts is
  the other writer of that key and wants a real completion from it, so the
  two must not share a run.

Gates: tag_lint PASS, playwright --list PASS (121 tests, unchanged). tsc
still fails on the pre-existing TS5102 baseUrl error in tsconfig.json, which
is identical on main and untouched here; a scoped re-check with baseUrl
removed shows zero type errors in this PR's own code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@CometActions

CometActions commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

🔍 Generated-test review

PR #8160OPIK-8102 [QA] Proposed e2e specs for the LLM-judge Gemini thinking level (from #8158 exploration)

CometActions · draft: yes · 8 files, 1 new spec (2 tests)
Base: AndreiCautisanu/OPIK-8102/flash-lite-thinking-none (a frontend feature branch, not main)

VERDICT: ready with notes — do not flip the draft flag yourself; a human must.

This is a good spec. Every factual claim it makes about the surface it drives, I
checked against the frontend source and all of them hold. Two reliability
defects were fixed here; the two remaining items are notes, and one of them is a
decision that is explicitly not mine to make.


Blockers (0)

None.


Fixed in this review (3)

  1. Brittle icon-class selector on the model-parameters gear
    pom/online-evaluation.page.ts:340
    Was this.dialog.locator('button:has(svg.lucide-settings2)'). lucide-settings2
    is an internal lucide-react class name, not an API; it is derived from the
    icon's PascalCase name at build time and a version bump renames it silently.
    lucide-react is pinned at exactly 0.461.0 (apps/opik-frontend/package-lock.json:13052)
    but the package is not installed on this machine, so I could not confirm
    whether that version emits lucide-settings2 or lucide-settings-2 — which
    is itself the argument for not depending on it.
    Fixed: added data-testid="model-parameters-trigger" to the gear in
    apps/opik-frontend/.../PromptModelSettings/PromptModelConfigs.tsx:125 and
    switched the POM to getByTestId. The POM's own docstring already said this
    was the right fix and "belongs in the FE" — per the skill this is a bucket-3
    test-only hook and belongs in this PR, not a follow-up. There is direct
    precedent: the add-edit-rule-dialog-submit testid this same POM already
    uses was added by a prior QA PR, 3a2c34baa3 [OPIK-6597] [QA] feat: Online Evaluation smoke (#6874). No role/label alternative exists — the button is
    icon-only and its "Model parameters" string lives in a Radix tooltip, which
    does not contribute an accessible name.

  2. Model-picker combobox matched by a hardcoded provider-name regex
    pom/online-evaluation.page.ts
    Was .filter({ hasText: /Select an LLM model|Gemini|Vertex AI|OpenAI|Anthropic|Claude|GPT/i }).
    That enumeration stops matching the day someone picks a provider outside the
    list, and it is a needless near-duplicate: PromptModelSelect.tsx:307
    already ships data-testid="select-a-llm-model".
    Fixed: scoped by has: getByTestId('select-a-llm-model') instead.

  3. Undocumented cross-spec contention on the workspace-global Gemini key
    fixtures/provider-key.fixture.ts
    The new geminiProviderKey fixture warns that specs using this fixture
    must not run concurrently, and mode: 'serial' handles that. But the other
    writer of that same key is playground-providers.spec.ts, which
    self-provisions Gemini through the AI Providers UI and then wants a real
    completion from it. If both ran at once it would adopt this fixture's dummy
    key and fail on auth. They do not collide today because that spec is
    @provider-sanity, which runs on its own cadence outside the tier ladder.
    Fixed: documented the interaction in the fixture, naming the spec.


Should fix (0)


Notes (2)

  1. Taxonomy gap — no capability names the model-settings pane. Escalating, not fixing.
    Both tests' real subject is the Gemini thinking-level control, and
    online-evaluation has no key for provider model parameters. The generator
    tagged them create-llm-judge-rule and edit-rule.

    Those tags are honest — I checked, and this is not the usual
    nearest-existing-key reach. Test 1 genuinely creates a rule and drives
    kebab → Edit → submit three times, asserting the persisted evaluator each
    time; test 2 genuinely creates one. If either capability broke, both tests
    fail. The taxonomy note: strings are candid about exactly what is and is
    not exercised, which is this estate's established convention, and the
    edit-rule note explicitly scopes itself ("Only the model-settings pane …
    the prompt, variable mapping, filters and sampling controls are still
    unexercised by an edit").

    So this is not a false green. But the distinctive behaviour these tests
    assert — which levels a model offers, which it preselects, and what a save
    persists into custom_parameters — is invisible on the coverage map. Your
    call, and only yours:
    add a capability (something like
    model-parameters / thinking-level) and tag it, or leave it as-is and
    accept that this behaviour is covered but uncounted. Per the hard rule I did
    not invent a key to make anything validate.

  2. The tsc gate is broken estate-wide and is currently blind. Not this PR.
    tests_end_to_end/e2e/tsconfig.json sets baseUrl, which TypeScript
    7.0.2
    (the installed version) removed outright. The file is byte-identical
    on origin/main, is untouched by this PR, and was last modified in
    2930c31491 ([OPIK-6126] [QA] feat: e2e infrastructure foundation (env config + workspace teardown) #6677).

    The consequence matters more than the cause: TS5102 aborts on the config,
    so npx tsc --noEmit typechecks zero files. The gate has not been
    verifying anything for the whole estate, not just this PR.

    I did not fix it, deliberately. Deleting the baseUrl line does not turn
    the gate green — it turns one config error into 94 real type errors across
    the estate, nearly all @types/node globals (process, Buffer,
    node:path) not resolving under TS7, plus a genuine pre-existing
    TS2300: Duplicate identifier 'deleteDashboard' at core/backend/client.ts:596
    and :1088 (both present on main). That is a TS7 migration and belongs in
    its own PR. Happy to open it if you want.

    To get the signal this PR needed, I ran tsc against a temporary config
    with baseUrl removed. Result: zero type errors in any code this PR wrote

    the new spec, the POM, the fixture, provider-keys.ts and both index
    re-exports are all clean, before and after my edits. The only errors in a
    PR-touched file are the pre-existing client.ts ones listed above. The temp
    config was deleted; it is not in the commit.


Bot comments

bot-comments.json is an empty array — baz and the other bots left no inline
review comments on this PR. Nothing to triage.

# Finding Verdict Disposition
(none posted)

Estate gates

Gate Before After my edits
tag_lint PASS PASS — 60 specs checked, 1 exempt, 0 problems
tsc FAIL FAIL (unchanged, pre-existing) — TS5102 baseUrl, identical on main; see Note 2. Zero errors in this PR's own code under a scoped re-check.
playwright --list PASS PASS — 2 tests collected from the new spec; full suite still 121 tests in 57 files, unchanged

What I verified

Every factual claim the spec makes about the frontend, checked against source at
the PR head:

  • gemini-3.5-flash-lite and gemini-3.5-flash exist as GEMINI models
    (types/providers.ts:827-828), and vertex_ai/gemini-3.5-flash-lite exists
    too (:860) — so the POM's insistence on scoping the model lookup to a
    provider group is a real hazard, not a hypothetical one.
  • Flash Lite offers exactly ['None','Minimal','Low','Medium','High'] and Flash
    exactly ['Minimal','Low','Medium','High'], in that order
    (modelUtils.ts:91-110) — matching both constants exactly.
  • Defaults are none for Flash Lite and medium for Flash
    (modelUtils.ts:244-249) — matching both preselection assertions.
  • The "none" level both declines to add a thinking block and strips a
    persisted one (AddEditRuleDialog/schema.ts, formClearsThinking), and
    custom_parameters is omitted entirely when it would be empty — so
    expect(customParameters).toBeNull() is the correct assertion, not an
    approximation.
  • Placement: tests/online-evaluation/ matches spec_dir: online-evaluation;
    the spec is added to the area's specs: list.
  • Tags: exactly one tier (@t2-cuj), exactly one area, all @cap: keys exist,
    all are string literals. Tier is right — the spec spends no LLM budget.
  • No waitForTimeout, no .skip/.fixme/.only, no fixed sleeps. All waits
    are web-first assertions or bounded toPass() polls with timeouts.
  • Teardown is entirely in fixtures. automationRulesCleanup is pre-existing and
    discovery-based — requesting it is the whole API, so the spec destructuring it
    without calling anything is correct usage, not a dangling parameter.
  • Entity names use testNamespace, which resolves to the swept
    ${cujPrefix}-w<worker>-<slug> shape (fixtures/base.fixture.ts:39-47).
  • Support code is bucket 1/2: getLlmJudgeModel, findProviderKeyByProvider,
    deleteProviderKeyById all extend existing patterns in files that predate the
    PR. deleteProviderKeyByName was refactored but kept behaviour-identical.
  • This PR carries no production behaviour change. The frontend files in
    git diff origin/main...HEAD come from the two base-branch commits, not from
    this PR. The one frontend line now in the diff is the data-testid I added.

What I could NOT verify

  • The tests have never been run. No Opik stack is available here, so both
    tests are unexecuted. Everything above is source-level verification of what
    they would assert — it is not evidence that they pass.
  • Whether lucide-react 0.461.0 emits lucide-settings2 or lucide-settings-2
    the package is not installed on this machine. Moot now that the selector is a
    testid, but it means I cannot say whether the original selector was already
    broken or merely fragile.
  • The frontend edit does not typecheck or lint hereapps/opik-frontend
    has no node_modules. It is a single JSX attribute on a Button that spreads
    ...props (ui/button.tsx:64,75) and whose ButtonProps extends the React
    button HTML attributes, so data-testid is valid and reaches the DOM; but the
    frontend's own CI is what will confirm that.
  • The backend's update semantics for an omitted custom_parameters. The
    spec's headline assertion — that setting None removes an already-persisted
    {thinking:{level:minimal}} — depends on the backend clearing the field when
    the payload omits it. I read the frontend half only. If the backend treats
    absent as "no change", this test fails loudly, which is the correct outcome:
    it would be catching a real bug in the base branch's fix, not a false green.

One thing to be aware of

This PR targets AndreiCautisanu/OPIK-8102/flash-lite-thinking-none, not
main. The data-testid I added therefore rides into that feature branch's PR
rather than landing independently — which is correct (the spec cannot run
without it) but worth a word to that PR's author, since it puts a frontend line
in their diff that they did not write.


review_generated_tests.yml ·
this PR stays a draft — a human decides when it is ready.

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

Labels

Frontend 🔴 size/XL tests Including test files, or tests related like configuration. typescript *.ts *.tsx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant