Skip to content

[OPIK-8204] [QA] test: cover the alerts CRUD lifecycle with E2E tests - #8117

Merged
AndreiCautisanu merged 5 commits into
mainfrom
andreicautisanu/OPIK-8204-e2e-alerts-crud
Sep 3, 2026
Merged

[OPIK-8204] [QA] test: cover the alerts CRUD lifecycle with E2E tests#8117
AndreiCautisanu merged 5 commits into
mainfrom
andreicautisanu/OPIK-8204-e2e-alerts-crud

Conversation

@AndreiCautisanu

Copy link
Copy Markdown
Contributor

Details

The alerts area had all 8 taxonomy capabilities uncovered — the only alerts coverage was the empty-state screenshot (empty-states.spec.ts E12) and the workspace-role permission matrix, neither of which exercises a list row or the alert editor. Alerts fire webhooks at customer endpoints, so an untested CRUD path is a real gap. This adds tests/alerts/ covering the happy-path spine, the two page objects, and an SDK-seeded fixture.

  • Teardown is mandatory rather than tidiness: alerts.project_id is a nullable CHAR(36) with an index and no FK (migration 000060), and ProjectService.delete only touches ProjectDAO, so an alert outlives the project that scoped it and surfaces in the next spec's list — the same hazard automationRulesCleanup documents for rules.
  • alerts.test-alert is deliberately left uncovered: "Test connection" POSTs the draft alert to whatever endpoint is in the form, so against example.com the mutation answers failure. A passing test there would only pin an error toast, so the reason is recorded in the taxonomy instead of claiming coverage. Needs a follow-up that stands up a receiver.
Capability Tier
alerts.list-alerts, alerts.create-alert, alerts.webhook-destination @t1-smoke
alerts.edit-alert, alerts.event-triggers, alerts.enable-disable, alerts.delete-alert @t2-cuj

Frontend changes

Four data-testids, all additive — no behavior change:

  • alerts-bulk-delete-button — icon-only button with no accessible name and no aria-label; there was no viable stable selector at all.
  • alert-name-input, alert-webhook-url-input — these <Label>s render without htmlFor over React-generated ids (:r1o:-form-item), so getByLabel cannot reach either field and the only available name is placeholder copy. Worth noting this is a genuine a11y gap a screen-reader user would hit, not just a test-tooling inconvenience — flagging for a separate fix rather than papering over it here.
  • alert-trigger-<eventType> — a trigger's title also renders in the test-webhook panel's accordion, so a text-scoped lookup resolves there and finds none of the config controls.

Change checklist

  • User facing
  • Documentation update

Issues

  • Resolves #
  • OPIK-8204

AI-WATERMARK

AI-WATERMARK: yes

  • Tools: Claude Code
  • Model(s): Claude Opus 5
  • Scope: Test authoring (spec, page objects, fixture), the four FE data-testid additions, and the taxonomy update.
  • Human verification: Author reviewed the diff. All specs were run locally against a self-built local OSS stack; regression-detection was confirmed by mutation testing (below), not by green runs alone.

Testing

Local OSS stack built from this branch (docker compose --profile opik up -d, frontend image rebuilt so the new testids are served), workspace default.

cd tests_end_to_end/e2e
npx playwright test tests/alerts/ --reporter=list --repeat-each=2   # 8 passed
python3 ../coverage/tag_lint.py --taxonomy ../coverage/taxonomy.yaml --estate ..   # 55 specs, 0 problems
npx playwright test tests/datasets/ --reporter=list --grep "@t1-smoke"   # 2 passed
pre-commit run --files <changed files>   # eslint + typecheck frontend: Passed

Mutation-tested, not just green. Breaking the Status column (accessorFn: () => true) and rebuilding the FE fails exactly the two status-asserting tests on toBeVisible and leaves the other two passing — so these assertions catch a real regression rather than passing vacuously. Verified again after the review refactor to confirm the wider timeouts didn't mask it.

Cold-start behaviour. An early version used the default 15s action timeout and failed reproducibly on a cold backend: the first concurrent burst after idle takes ~18-20s (measured 17.7s/18.7s response times on plain GETs, all 200s — the backend answers, just slowly), so every worker's opening step timed out with the page still on skeleton rows. The readiness waits now allow 30s, the same allowance experiment-detail, online-evaluation and configuration already use. Verified against a deliberately cold stack (docker restart opik-backend-1): 4/4 pass at 14-17s, precisely the window that previously failed. Then 24/24 across repeat runs.

Teardown verified by global-teardown reporting no alerts to sweep on every run — the fixture cleans up rather than leaning on the run-prefix backstop.

Not run: the full suite. Scoped to the new specs plus a sibling directory, since fixtures/index.ts is on every spec's import path; npx playwright test --list confirms all 115 tests across 52 files still enumerate.

Pre-existing, not touched: tests_end_to_end/e2e/tsconfig.json still sets baseUrl, removed in TypeScript 7, so a bare npx tsc --noEmit fails on a clean tree (confirmed by stashing this branch's changes). Typechecking with that line dropped shows 0 errors in the new files. Probably worth its own cleanup ticket.

Documentation

None — internal test coverage only.

🤖 Generated with Claude Code

The alerts area had all 8 taxonomy capabilities uncovered — only the
empty-state screenshot and the workspace-role permission matrix touched
it, neither exercising a list row or the alert editor.

Adds tests/alerts/ with the happy-path spine (list, create, edit,
event-triggers, enable-disable, delete), the two page objects, and an
SDK-seeded fixture. Teardown is mandatory rather than tidiness: alerts
hold project_id as a plain indexed column with no FK and
ProjectService.delete only touches ProjectDAO, so they outlive the
project that scoped them.

Four data-testids are added alongside. The bulk-delete button is
icon-only with no accessible name, and the Name/Endpoint URL inputs
render labels without htmlFor over React-generated ids, so getByLabel
cannot reach them; the per-trigger block needs one because its title
also appears in the test-webhook panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AndreiCautisanu
AndreiCautisanu requested review from a team as code owners September 2, 2026 10:42
@github-actions github-actions Bot added Frontend tests Including test files, or tests related like configuration. typescript *.ts *.tsx 🔴 size/XL labels Sep 2, 2026
@CometActions

CometActions commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Already covered by a test in this PR.

The five frontend files are test affordances only — three data-testids, the shared alertTriggerTestId helper and the bulk-delete button id — with no behaviour change, and the spec this PR ships is what consumes them: tests_end_to_end/e2e/tests/alerts/alerts-crud.spec.ts drives create through the form and re-opens the edit form to assert the webhook URL persisted, edits name/triggers/enabled and round-trips the threshold and window, and deletes one alert then bulk-deletes the rest with an API check that nothing is left. That covers the seven capabilities the taxonomy diff flips to covered, so there is nothing here for a second test to add. One note for review rather than triage: alert-editor.page.ts re-implements the :- slug inline instead of importing alertTriggerTestId, so the shared helper is shared by comment only.

Not testable yet. alerts.test-alert stays uncovered, and the taxonomy comment in this PR is right about why: Test connection / Test trigger in TestWebhookSection.tsx POST the draft alert to the endpoint in the form, so against example.com only the failure toast is assertable. Recording it as a deferral rather than letting it sit as the one covered: false nobody re-reads — the missing piece is estate-side, not yours.

areas: alerts

Run

Advisory, from the QA test radar. Nothing here blocks this PR, and anything it proposes is a draft for review.

Re-checked after a push on 03 Sep 10:13 UTC — nothing the verdict depends on changed.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

⏱️ pre-commit per-hook timing

Hook Description Result Duration
🌐 typecheck — frontend Whole-project tsc type check 31.16s
🌐 eslint — frontend Lint + autofix JS/TS 3.62s
Total (2 ran) 34.78s
⏭️ 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 thread tests_end_to_end/e2e/pom/alerts.page.ts
Comment thread apps/opik-frontend/src/v2/pages/AlertsPage/AddEditAlertPage/EventTriggers.tsx Outdated
Comment thread tests_end_to_end/e2e/tests/alerts/alerts-crud.spec.ts Outdated
Comment thread tests_end_to_end/e2e/tests/alerts/alerts-crud.spec.ts Outdated
Comment thread tests_end_to_end/e2e/fixtures/alert.fixture.ts
… slug

Trigger titles were asserted as one comma-joined string, but AlertDAO.FIND
aggregates triggers with JSON_ARRAYAGG and no ORDER BY and AlertsEventsCell
joins them in API order, so the order is not guaranteed — a latent flake
that happened to pass locally. Now asserted as membership.

registerAlertCleanup took an id read off the rendered row, so a failure
between the create and the registration left the alert to the run-level
sweep. Replaced with uiAlertCleanup, which discovers alerts by the test's
namespace prefix at teardown — verified by injecting a failure immediately
after the create and confirming global-teardown finds nothing to sweep.

The event-type map defined 4 of 10 types, so configureThresholdTrigger
rejected trace:errors and trace:latency despite both rendering the
threshold controls it exists to fill.

Trigger test ids now normalize the wire value's colon via a shared
alertTriggerTestId helper, so the selector reads alert-trigger-trace-cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread tests_end_to_end/e2e/fixtures/alert.fixture.ts Outdated
Comment thread tests_end_to_end/e2e/fixtures/alert.fixture.ts Outdated
The editor cannot create two triggers of the same event type — its popover
binds each checkbox to selectedEventTypes.has(type) — but the create API
accepts a duplicate pair, and an alert seeded that way renders two
identical config blocks that triggerConfig matches at once.

No spec reaches that state, so this is a POM contract hazard rather than a
live bug. Guarded rather than resolved with .first(), which would silently
drive one of two indistinguishable triggers; configureThresholdTrigger now
explains the shape instead of surfacing a bare strict-mode violation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread tests_end_to_end/e2e/pom/alert-editor.page.ts Outdated
…ger case

uiAlertCleanup deleted every alert matching the test's namespace prefix.
testNamespace truncates the test title to 40 characters, so two similarly
named tests share a prefix and one teardown could delete the other's
alerts. The test now declares the names it will create before creating
them, and teardown deletes only exact matches — the prefix survives purely
to narrow the workspace-wide read. Declaring up front keeps the property
that a mid-flow failure cannot skip cleanup, re-verified by injecting a
throw immediately after the create.

assertSingleTriggerConfig let a zero count through, so a missing trigger
surfaced as a fill against a zero-match locator rather than as an
explanation. Now rejects count !== 1 with a distinct message per case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread tests_end_to_end/e2e/fixtures/alert.fixture.ts
natagh23
natagh23 previously approved these changes Sep 3, 2026
Conflict in fixtures/index.ts: main's id-aged-traces fixture and this
branch's alert fixture both extended dashboard-cleanup and both claimed
the chain tail, so index.ts re-pointed line 1 two different ways.

Resolved by chaining rather than choosing — alert.fixture now extends
id-aged-traces, making the chain dashboard-cleanup -> id-aged-traces ->
alert, and index.ts re-exports from alert with both sides' type exports
kept. Picking either side alone would have silently dropped the other
fixture from every spec's `test` object.

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

@aadereiko aadereiko left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

@AndreiCautisanu
AndreiCautisanu merged commit 2da58aa into main Sep 3, 2026
23 checks passed
@AndreiCautisanu
AndreiCautisanu deleted the andreicautisanu/OPIK-8204-e2e-alerts-crud branch September 3, 2026 13:02
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.

4 participants