Skip to content

[OPIK-7707] [QA] Proposed e2e specs: dataset version counters on the delete path - #7965

Closed
CometActions wants to merge 5 commits into
mainfrom
comet-qa-bot/OPIK-7707/qa-e2e-dataset-version-delete-counters
Closed

CometActions wants to merge 5 commits into
mainfrom
comet-qa-bot/OPIK-7707/qa-e2e-dataset-version-delete-counters

Conversation

@CometActions

@CometActions CometActions commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Generated by the release QA side flow (release-test-proposal). Not reviewed by a human. Draft on purpose — please review before promoting.

Where these came from

Exploratory testing of #7705[OPIK-7707] [BE] perf: make dataset version count updates atomic, run by hand against that PR's own deployed environment (pr-7705.dev.comet.com, serving 2.2.14-7705-merge-3043; fresh OSS install, workspace default). Every flow below was reached and verified manually there before any spec was written.

This PR targets danield/OPIK-7707-make-the-version-count-update-atomic, not main — it is branched from that PR's head, 7ad36cf35490e454fb93810311c374361354a67f. The specs assert the counter arithmetic the way #7705 implements it (one signed-delta UPDATE shared by the insert and delete paths), so they belong on top of the change they test rather than ahead of it. Merge #7705 first; this then rides in with it.

What was uncovered

updateVersionCountsForInsert and updateVersionCountsForDelete now funnel through updateVersionCounts(versionId, workspace, totalDelta, addedDelta, modifiedDelta, deletedDelta, user) — six positional ints, which is where a sign or argument slip would hide. Nothing in the suite reads a version counter on the delete side: dataset-items.spec.ts bulk-deletes but counts rendered table rows and SDK items, so an items_total that kept climbing after a delete would leave every existing spec green, while the Version history tab showed users the wrong Item count.

datasets.version-history-view was covered: false in the taxonomy at this branch point.

The specs

Both live in tests_end_to_end/e2e/tests/datasets/dataset-version-delete-counters.spec.ts, both @t2-cuj @area:datasets @cap:datasets.version-history-view, and both drive the ungrouped write path (no batch_group_id, so the latest version is mutated in place instead of a new one being cut). Writes go through the backend client; the figures are then read back off the UI — the disagreement between the two surfaces is the thing worth catching.

1. Deleting items moves only the total and deleted counters of the version it mutates, and the Version history tab renders them

Seeds via the dataset fixture (3 items, v1), adds 3 more, deletes 2 by id, then re-sends the identical delete. After every step it asserts the exact quadruple, that the version id did not change and no second version appeared, and that items_total equals the item ids the dataset actually holds — {3,3,0,0}{6,6,0,0}{4,6,0,2}, unchanged on the repeat. The repeat also asserts 204, not 404: the deletedCount == 0 short-circuit has to fire before the counter update, or a repeated delete writes a phantom delta. Finally the Version history tab must render Item count 4 and exactly two change tags, + 6 and − 2 — a ~ tag appearing there is a failure.

Result: passed.

2. Interleaved inserts, upserts and deletes keep the version counters equal to the items the dataset holds

A single operation can't catch a delta applied to the wrong column — after one write the numbers still look plausible. This drives insert 4 → delete 2 → insert 3 → upsert 2 stored ids with new content → delete 1, all into v1, asserting the full quadruple and the stored id set after each: {3,3,0,0}{7,7,0,0}{5,7,0,2}{8,10,0,2}{8,10,2,2}{7,10,2,3}. Then the tab must show Item count 7 and exactly three tags, + 10 ~ 2 − 3.

Result: passed.

Verification

cd tests_end_to_end/e2e
npx tsc --noEmit                                          # clean
python3 ../coverage/tag_lint.py --taxonomy ../coverage/taxonomy.yaml --estate ..
                                                          # 27 specs checked, 0 problem(s)
npx playwright test tests/datasets/ --reporter=list        # 7 passed (31.9s)

Run against https://pr-7705.dev.comet.com (OPIK_DEPLOYMENT=oss, workspace default). The whole tests/datasets/ directory was run, not just the new file, because DatasetItemsPage is shared — the 5 pre-existing specs still pass.

Two things a reviewer should know about that run:

  • The suite's makeBackendClient requires an API key for any non-localhost host, so the run passed a placeholder OPIK_API_KEY at an OSS install that has no auth. That's a property of running the OSS suite against a remote OSS deployment, not something the specs need.
  • To prove the UI assertions can actually discriminate rather than passing vacuously, the expected Item count was temporarily mutated from 4 to 5 and the spec failed at exactly that line. Reverted before committing.

What I deliberately did not write

One candidate was dropped: concurrent batch inserts and deletes into one version (8 workers × 5 batches × 10 items → items_total exactly 401; concurrent insert+delete → 401/441/0/40). It passed by hand on the PR environment, but it was marked weak by the exploration and I agree: withDatasetVersionLock still serialises these API paths, so the assertion would be about end-to-end exactness rather than the lock-free property the PR claims — the DAO-level proof is the PR's own unit test — and a wall-clock-sensitive fan-out is a flake risk the suite shouldn't take on for that. If the team wants a single-threaded stand-in with similar value, the deterministic one is a >1000-row CSV upload (csvBatchSize is 1000), which drives several sequential increments into one version through a real product path.

Also not proposed: the COALESCE(col, 0) NULL-counter guard (not producible through the API — every INSERT INTO dataset_versions names the columns and every DatasetVersion.builder() sets them; the PR's own unit test forces it with raw SQL), and a second datasets.bulk-delete-items spec (already covered at t3-nightly; the counter assertions belong under the uncovered capability instead).

Supporting changes

  • core/backend/client.tsgetDatasetVersions, listDatasetItemIds, upsertDatasetItemsIntoLatestVersion, deleteDatasetItemsByIds. The delete helper returns the HTTP status rather than void so a caller can tell a specified no-op 204 from a 404.
  • pom/dataset-items.page.ts — Version history tab: openVersionHistory, versionHistoryRow, versionItemCount, versionChangeSummary, versionChangeTags, versionChangeTag. Cells are addressed by the shared DataTable's data-cell-id, not by position, because column order is user-configurable.
  • coverage/taxonomy.yaml — spec added to the area's specs: list, datasets.version-history-view flipped to covered: true, tier: t2-cuj with a note on what it does and does not assert.

One merge note

main has moved on since this branch point: it now carries datasets/dataset-version-counters.spec.ts, which covers the insert side of the same capability (multi-batch and multi-threaded insert(), adds/modified), and already has version-history-view flipped to covered. That spec and this one are complementary — nothing here re-asserts the insert path — and the filenames deliberately don't collide. Expect small textual conflicts in taxonomy.yaml (the specs: list, the capability line) and in core/backend/client.ts / pom/dataset-items.page.ts, where main has near-identical versions of getDatasetVersions, listDatasetItemIds, openVersionHistory and versionItemCount; take main's and keep the additions here that it lacks (DatasetVersionRef.id, the write/delete helpers, the change-summary locators).


Generated by the release QA side flow. Needs human review before merge.

JetoPistola and others added 5 commits August 3, 2026 08:19
Replace the read-modify-write count update on the dataset item insert path
with a single atomic SQL increment, and drop the version re-read that
immediately followed it. MySQL round-trips inside the per-dataset lock drop
from three (find, update, re-read) to one.

The returned DatasetVersion is read by no consumer -- the REST endpoint
returns 204 with the blocked result unassigned, and saveBatch maps it to the
item count -- so insertItemsIntoVersion now completes empty instead of
fabricating a partially populated object.

The delete path is converted to the same atomic increment for consistency.
Unlike the insert path it saves no round-trip: both callers already fetch the
version for their own logging. What it gains is that the counter arithmetic no
longer depends on withDatasetVersionLock for mutual exclusion.

Implements OPIK-7707: Make the version count update atomic and drop the
post-insert version re-fetch

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
updateVersionCountsForDelete ignored the affected-row count from
incrementCounts, so insert and delete disagreed about how a missing or
cross-workspace version is handled. Both callers currently fetch the version
first via getVersionById, which already throws, so this is not reachable today
-- but the asymmetry is a trap for later work that narrows the lock or drops
that pre-fetch.

Also drop the helper's log.info: the delete callers already emit a summary with
deletedCount, versionId and the resulting total two frames up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
updateVersionCountsForInsert and updateVersionCountsForDelete had become
identical apart from which deltas they pass -- same transaction, same DAO call,
same zero-row NotFoundException guard -- so any change to the atomic update or
the error handling had to be mirrored in both.

Both now delegate to a shared updateVersionCounts(...) taking signed deltas. The
named wrappers stay so the call sites still read as insert/delete intent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The counter columns are `INT DEFAULT 0` (migration 000036), which is
nullable -- DEFAULT only applies when the column is omitted on INSERT,
and DatasetVersion maps all four as boxed Integer. A bare
`col + :delta` leaves NULL as NULL, which then unboxes to an NPE on read.

The absolute update this replaced happened to repair a NULL by
overwriting it, so the increment has to COALESCE explicitly or it is a
regression in NULL tolerance.

Adds a test that nulls the counters directly and asserts the increment
treats them as zero. Verified it fails without the COALESCE.

Ported from the duplicate work on #7928, which is being closed in favour
of this PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OPIK-7707 (#7705) put both dataset-version write paths behind one
signed-delta UPDATE. The insert side is observable through existing
specs only as rendered rows; the delete side is not covered at all —
`dataset-items.spec.ts` bulk-deletes but counts table rows and SDK
items, never a counter, so an items_total that kept climbing after a
delete would leave the suite green.

Adds `datasets/dataset-version-delete-counters.spec.ts`: two @t2-cuj
specs driving the ungrouped write path (no batch_group_id, so the
latest version is mutated in place) and asserting, after every
operation, the exact counter quadruple, that the version was mutated
and not replaced, and that items_total equals the item ids the dataset
actually holds — then reading the same figures back off the Version
history tab.

Also adds the backend-client reads those need (getDatasetVersions,
listDatasetItemIds, ungrouped upsert/delete-by-id) and the
DatasetItemsPage locators for the Version history tab, and flips
datasets.version-history-view to covered in the taxonomy.

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 Aug 24, 2026
@github-actions

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

Copy link
Copy Markdown
Contributor

⏱️ pre-commit per-hook timing

No linted files changed — nothing to run.

⏭️ 41 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 ⏭️
🌐 eslint — frontend Lint + autofix JS/TS ⏭️
🌐 typecheck — frontend Whole-project tsc type check ⏭️
📘 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 ⏭️

Comment on lines +257 to +259
async getDatasetVersions(datasetId: string): Promise<DatasetVersionRef[]> {
const page = await opik.api.datasets.listDatasetVersions(datasetId, { size: 100 });
const content = page.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.

Version history silently truncates

getDatasetVersions returns only the first 100 results as a complete-looking DatasetVersionRef[], so datasets with more versions are silently truncated and callers can make incorrect version-count conclusions — should we fetch all pages or expose an explicit pagination contract?

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 257-269, refactor
`getDatasetVersions` so it does not silently truncate the paginated version history at
100 results. Fetch successive pages using the API’s pagination parameters until the
final page is reached, accumulate all results, and then map them to `DatasetVersionRef`;
add or update coverage for datasets containing more than 100 versions.

Comment on lines +261 to +267
id: String(v.id ?? ''),
versionName: String(v.versionName ?? ''),
itemsTotal: Number(v.itemsTotal ?? 0),
itemsAdded: Number(v.itemsAdded ?? 0),
itemsModified: Number(v.itemsModified ?? 0),
itemsDeleted: Number(v.itemsDeleted ?? 0),
isLatest: Boolean(v.isLatest),

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.

Missing counters silently pass as zero

The adapter’s ?? 0 defaults let omitted or null itemsModified/itemsDeleted values satisfy the seeded-version assertion without verifying the API response. Because parseOrThrow uses skipValidation: true, Boolean(v.isLatest) also turns malformed values such as "false" into true — should we validate required finite numeric/boolean fields and throw with their names instead of defaulting or coercing them?

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 261-267, update
`getDatasetVersions` so it does not default missing/null counters to zero or coerce
`isLatest` with `Boolean(...)`. Explicitly validate that each required counter is a
finite number and that `isLatest` is actually a boolean, throwing an error that names
the invalid field when validation fails; preserve valid zero and false values.

Comment on lines +323 to +326
async deleteDatasetItemsByIds(itemIds: string[]): Promise<number> {
const { rawResponse } = await opik.api.datasets
.deleteDatasetItems({ itemIds })
.withRawResponse();

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.

Repeat-delete test bypasses idempotent branch

The item-ID-only helper sends itemIds, so once the first ungrouped ALTER ... DELETE is visible, resolveDatasetIdsFromItemIds makes getDatasetIdOrResolveItemDatasetId return Mono.empty() before deleteItemsFromExistingVersion/removeItemsFromVersion, yet the resource returns HTTP 204 and the assertion skips the documented existingCount == 0 no-counter path; if rows remain visible, it can instead delete and count them again, making the test timing-dependent. Since DatasetItemsDeleteValidator rejects adding dataset_id alongside item_ids, should we add a backend-supported scoped-ID retry contract or exercise the no-op branch through a supported path?

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 323-326, fix
deleteDatasetItemsByIds and its callers so a repeated ungrouped delete reliably reaches
the documented existingCount == 0 no-op path instead of being skipped by dataset-ID
resolution or depending on ClickHouse visibility timing. Do not add datasetId alongside
itemIds, because DatasetItemsDeleteValidator rejects that request; implement a
backend-supported scoped-ID retry contract if available, otherwise change the test to
use a supported deletion path that reaches and asserts the no-counter branch.

Comment on lines +80 to +84
versionHistoryRow(versionName: string): Locator {
return this.versionsTableBody
.locator('tr[data-row-id]')
.filter({ has: this.page.getByRole('cell', { name: versionName, exact: true }) });
}

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.

Version-history POM failures lack named steps

versionHistoryRow, versionItemCount, versionChangeSummary, versionChangeTags, and versionChangeTag return locators without named test.step(...) boundaries, so the outer spec step doesn't provide the required per-method POM boundary — should we wrap each method in a descriptive step and return its callback result, as .agents/skills/writing-e2e-tests/SKILL.md and .agents/skills/writing-e2e-tests/conventions.md require?

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/dataset-items.page.ts` around lines 80-124, update
`versionHistoryRow`, `versionItemCount`, `versionChangeSummary`, `versionChangeTags`,
and `versionChangeTag` to comply with the E2E POM convention requiring every method to
use a named `test.step(...)` boundary. Wrap each method body in a descriptive step and
return the locator from the step callback, preserving the existing locator behavior and
method signatures.

return { versionId: version.id, itemIds };
});

let held = seeded.itemIds;

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.

Obscures stored item ID invariant

held is ambiguous for the full set of stored dataset item IDs passed as expected.itemIds — should we rename it to itemIds (or currentItemIds) and update all uses, including the later held.slice(...) calls?

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/tests/datasets/dataset-version-delete-counters.spec.ts` around
lines 202-263, rename the `held` variable in the interleaved inserts, upserts, and
deletes test to `currentItemIds` (or `itemIds`) so it clearly represents the complete
set of currently stored dataset item IDs. Update every assignment, filter, spread,
`expected.itemIds` reference, and later `held.slice(...)` call consistently.

@JetoPistola
JetoPistola force-pushed the danield/OPIK-7707-make-the-version-count-update-atomic branch from 624b8dc to bf83f88 Compare August 26, 2026 11:05
Base automatically changed from danield/OPIK-7707-make-the-version-count-update-atomic to main August 30, 2026 06:00
Comment on lines +5321 to +5325
int expectedIncrements = writers * incrementsPerWriter;
var version = getLatestVersion(datasetId);
assertThat(version.itemsTotal()).isEqualTo(1 + expectedIncrements);
assertThat(version.itemsAdded()).isEqualTo(1 + expectedIncrements);
assertThat(version.itemsModified()).isEqualTo(2 * expectedIncrements);

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.

Concurrent test misses delete-counter corruption

The concurrent increment test doesn't check itemsDeleted, so a regression that changes this counter would still pass — should we assert version.itemsDeleted() remains zero?

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
`apps/opik-backend/src/test/java/com/comet/opik/api/resources/v1/priv/DatasetVersionResourceTest.java`
around lines 5321-5325, update
`incrementCounts__whenConcurrentWritersBypassTheLock__thenCountersAreExact` to verify
the complete counter invariant. Add an explicit assertion that `version.itemsDeleted()`
remains zero, since the test passes a zero deleted delta and should detect regressions
that modify or overwrite that counter.

Comment on lines +557 to +561
int incrementCounts(@Bind("version_id") UUID versionId,
@Bind("items_total_delta") int itemsTotalDelta,
@Bind("items_added_delta") int itemsAddedDelta,
@Bind("items_modified_delta") int itemsModifiedDelta,
@Bind("items_deleted_delta") int itemsDeletedDelta,

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.

Misleading name hides decrement behavior

incrementCounts applies signed deltas, including the negative itemsTotalDelta from updateVersionCountsForDelete, so its name can mislead callers about deletion support and argument signs — should we rename it to applyCountDeltas and update the Javadoc/callers?

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
apps/opik-backend/src/main/java/com/comet/opik/domain/DatasetVersionDAO.java around
lines 557-561, rename `incrementCounts` to an explicit signed-delta name such as
`applyCountDeltas`, since deletion callers pass negative values and the SQL adds signed
deltas. Update the method’s Javadoc and every caller, including
`updateVersionCountsForDelete`, so the API clearly communicates that positive and
negative counter adjustments are supported.

log.info("Inserting '{}' items into existing version '{}'", batch.items().size(), latestVersionId);

return insertItemsIntoVersion(batch, datasetId, latestVersionId, workspaceId, userName);
return insertItemsIntoVersion(batch, datasetId, latestVersionId, workspaceId, userName).then(Mono.empty());

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.

Versioned append loses promised result

The existing-version paths in save(DatasetItemBatch) call insertItemsIntoVersion(...).then(Mono.empty()), so callers that block() or subscribe receive no DatasetVersion after a successful append—should we emit the mutated version from both branches?

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
`apps/opik-backend/src/main/java/com/comet/opik/domain/DatasetItemService.java` around
lines 1665 and 2359-2360, fix `mutateLatestVersionWithInsert` and
`handleGroupedInsertion` so successful updates emit the resulting `DatasetVersion`
instead of completing with `Mono.empty()`. Refactor `insertItemsIntoVersion` or append a
properly scheduled fetch after the insert and atomic count update, ensuring both
existing-version paths satisfy the `save(DatasetItemBatch)` return contract while
preserving the new-version behavior.

Comment on lines +90 to +95
* Addressed by the table's own `data-cell-id` (`<rowId>_<columnId>`) rather
* than a positional nth(): the version table has no per-cell testid, and
* column order is user-configurable, so position is not stable.
*/
versionItemCount(versionName: string): Locator {
return this.versionHistoryRow(versionName).locator('[data-cell-id$="_items_total"]');

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.

Brittle CSS locators bypass required test IDs

The POM relies on structural [data-cell-id$=...] CSS locators, so column-order changes make the version-history tests brittle — should we add scoped descriptive kebab-case data-testid attributes through the existing change_summary cell components and a page-local items_total renderer, then use getByTestId as .agents/skills/playwright-pom-discovery/SKILL.md and .agents/skills/writing-e2e-tests/conventions.md require?

Severity web_search

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/dataset-items.page.ts` around lines 90-95, update the
version-history cell locator logic to stop relying on structural `[data-cell-id$=...]`
CSS selectors. Add descriptive, scoped kebab-case `data-testid` attributes to the
frontend renderers for the `items_total` and `change_summary` cells, then have
`versionItemCount` and `versionChangeSummary` locate those cells with `getByTestId`
while preserving row scoping; do not modify the generic DataTable.

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

Labels

baz: pending 🟠 size/L 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.

3 participants