Qa/new UI suite wave 0#3303
Conversation
…ort, product-qa) Wave 0 (foundations, per the new-UI conversion handoff): - F1: author tests/e2e/NEW_UI_HOUSE_STYLE.md (normative §1/§5 cited by existing page objects; absorbs the D1–D6 convention decisions). - F2: promote the copy-pasted DataViews helpers to utils/dataViews.ts; admin's tests/e2e/admin/adminDataViews.ts becomes a re-export (its ~29 importers keep their path). Refactor new-withdraw/new-orders/new-seller-badge/new-store-reviews/ orders/vendor-products page objects to delegate (public APIs unchanged). - F3: consolidate announcement-modal dismissal on @utils/helpers closeAnnouncementModal (removed inlined copies in the new-UI page objects). - F4: utils/authStates.ts exports the storage-state path constants; migrate the 11 new-* folders + product-form-manager/newProductForm* + announcementsNewUI. - F5: normalize @new-ui tags (announcementsNewUI + the in-file "(React)" blocks that truly drive /dashboard/new/, per the D4 rule); add npm run test:e2e:newui. - F6: backfill feature-map.yml with new-UI entries. - D5: fix the swapped subscription smoke URLs (product-subscription ↔ packs). - D6: retire social-linking + store-seo smokes (superseded by new-social/new-store-seo). - Fix a load-race in new-seller-badge tabsVisible() (bounded wait, no weakening). Wave 1 (first two items, each green 3× — 2 headless + 1 headed — against Docker): - new-store-support/ (B2): 14 vendor + 2 cross-role REST tests on /dashboard/new/#/support(+/:id). Legacy vendor cases nested-describe.skip'd (admin module-disable case kept active, D3); legacy-URL "(React)" smokes retired. - new-product-qa/ (B5): 9 vendor + 1 business-flow test on /dashboard/new/#/product-questions-answers(+/:id). Legacy vendor cases nested-describe.skip'd (customer/guest/admin kept, D3); transitional smokes retired. Notes: - new-products status-tab tests are a PRE-EXISTING flaky, already-converted spec (unchanged logic; documented in tests/e2e/new-products/bugs/status-tab-flakiness.md), not a Wave 0 regression. - Progress tracked in tests/pw/CONVERSION-LOG.md. Shard-duration baseline refresh is deferred to after all waves (per the handoff's final step).
Net-new React vendor "Admin Support" coverage (Pro module vendor_support) at
/dashboard/new/#/vendor-support(+/:id). No legacy vendor spec exists, so nothing
is skipped (the wp-admin adminVendorSupport suite is a different SPA and stays).
- newVendorSupportPage.ts + newVendorSupport.spec.ts: 13 vendor + 3 cross-role
tests, green 3× (2 headless + 1 headed) against Docker :9999.
- Behavioral coverage: list mount + All/Active/Closed tabs, vendor-context
columns (asserts the admin-only Vendor column + Delete row-action are ABSENT),
Closed-tab filter, create via the Add-New-Ticket DokanModal (Quill body),
subject search + empty state, row-action Close through the destructive
alertdialog with a REST status oracle, detail thread, vendor reply,
reply-reopens-closed, HashRouter reload; cross-role (admin acts via REST):
admin reply surfaces in the vendor timeline, a create→admin-close business
flow, and customer non-mount.
- Seeding raw via /dokan/v1/vendor-support/tickets (vendorAuth create so the
ticket is vendor-owned; adminAuth for the admin reply + cleanup DELETE),
guarded by activateModules('vendor_support').
- feature-map: new 'Vendor Support' page entry (13 + 3 new-UI leaves); flagged
inline that the wp-admin adminVendorSupport.spec.ts (14 cases) was never mapped.
React vendor Staff parity (Pro module vendor_staff) at
/dashboard/new/#/staffs(+/create, /update/:id, /permissions/:id). Ported from
the legacy vendor-staff spec (100% no-op stub — vacuous green).
- newVendorStaffPage.ts + newVendorStaff.spec.ts: 9 vendor + 3 staff + 1
admin-gate = 13 tests, green 3× (2 headless + 1 headed) against Docker :9999.
- Vendor: list render, create (form), edit, grant/reset permissions (checkbox
id == capability key), delete (destructive alertdialog), Name-link → edit,
row-action menu, reload — each with a REST oracle (capabilities map, staff
existence) plus the React list-row.
- Staff (real my-account login via a local frontendLogin helper — no staff
storage state exists): default-cap sidebar shows Products/Orders and hides
Withdraw/Staff; a vendor-grants-capability-via-REST → staff-sidebar-gains-menu
cross-role flow; staff denied the staff-management route (Permission Denied).
- Admin: module deactivate (REST) → Staff menu removed → reactivate → menu
restored (ports the legacy enable/disable-module motive).
- Seeding: createVendorStaff(vendorAuth) / updateStaffCapabilities / raw DELETE
{id, force:true}; beforeAll dedupe of leftover PW- staff.
- Live learnings: Name cell is a `!dokan-link` div not an <a>; DokanToaster
toasts render empty + auto-dismiss (never assert toast text — REST/list-row
oracles instead); form-created user_login is WP-sanitized + getAllVendorStaffs
paginates (create oracle is the newest-first list-row).
- Legacy retired: describe 2 (stub-backed via new ApiUtils(null)) + describe 3
(smokes on a non-existent dashboard/vendor-staff/ URL) → describe.skip with
pointers. feature-map: 13 new-UI leaves under 'Vendor Staff Manager'; legacy
vendor/staff leaves flipped to false.
React vendor Return Request (RMA) parity (Pro module rma) at
/dashboard/new/#/return-request(+/:id). Ported from the legacy
vendor-return-request spec (100% no-op stub with a fake UI-checkout seed).
- newReturnRequestPage.ts + newReturnRequest.spec.ts: 9 vendor + 1 cross-role =
10 tests, green 3× (2 headless + 1 headed) against Docker :9999.
- Covers list render, status-tab filter, details (Order/Reason cards), send RMA
message, update status, delete, send refund (masked DokanPriceInput modal +
admin pending-refund REST oracle), not-found, reload; cross-role: a
customer-created request appears on the vendor list.
- Seeding is REST-only (RMA lives in custom tables wp_dokan_rma_* whose cache is
invalidated only via module actions — raw SQL serves stale reads): fresh WC
order -> POST warranty-requests as customerAuth (create is customer-only) with
a real line-item id -> recover the id via GET ?order_id as vendor; seed the
target status at create time (PUT of an unchanged status errors).
- Live learning: the row Delete is a TWO-LAYER confirm — plugin-ui destructive
alertdialog ("Delete") then the module's DokanModal ("Yes, Delete") which
fires the actual DELETE.
- Legacy retired: the already-skipped RMA describe gets a pointer; the
transitional "(React)" smokes (legacy URL) -> describe.skip. RMA settings stay
legacy (no React route). feature-map: 10 new-UI leaves under 'Return and
Warranty Request'; legacy vendor leaves flipped to false.
React vendor Verification settings parity (Pro module vendor_verification) at /dashboard/new/#/settings/verification — a CARD list (not DataViews). Ported from the legacy vendor-verifications spec (100% no-op stub incl. its own stub ApiUtils — vacuous green). - newVendorVerificationsPage.ts + newVendorVerifications.spec.ts: 9 vendor + 1 cross-role = 10 tests, green 3x (2 headless + 1 headed) against Docker :9999. - Covers: view page, submit-with-wp.media-upload, no-document validation, resubmit-rejected, cancel-pending (DokanModal "Yes, Cancel"), view documents, view note, approved-has-no-actions, reload; cross-role: an admin-approved request reflected on the vendor card. - Seeding REST-first via the real @utils: activateModules, one shared uploadMedia(avatar) (also makes the wp.media Library non-empty), createVerificationMethod/Request (adminAuth sets any status), updateVerificationRequest for admin approve; cleanup deletes each method. - Notes: no headless-hang (the flagged risk); the wp.media Library recipe works headless; readiness keys on the always-present "Social Profiles" card; the page fetches once so mid-test REST writes are asserted after a fresh goto. - Legacy retired: the 6 vendor-dashboard cases nested-describe.skip'd (admin, setup-wizard [different surface], customer stay active); the vendor "(React)" smokes (legacy URL) -> describe.skip; admin "(React)" smokes left (different surface, covered elsewhere). feature-map: 10 new-UI leaves under 'Vendor Verification'; the 6 legacy vendor-dashboard leaves flipped to false. Wave 1 is now COMPLETE: all six items (store-support, product-qa, vendor-support, vendor-staff, return-request, verifications) green 3x, legacy blocks skipped with pointers, feature-map backfilled.
📝 WalkthroughWalkthroughThis PR adds shared Playwright auth-state and DataViews helpers, updates feature-map coverage, refactors existing React page objects/specs to those helpers, introduces many new React vendor-dashboard specs/page objects, tags existing tests with ChangesPlaywright React "new-UI" test suite conversion
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
tests/pw/tests/e2e/withdraws/withdraws.spec.ts (2)
138-155: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winReplace the fixed 3s sleep with a readiness check.
waitForTimeout(3000)is a hard-coded delay that slows the test and can still flake; wait on a concrete signal instead, such as the balance widget or a loading indicator.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/withdraws/withdraws.spec.ts` around lines 138 - 155, The withdraw page test is using a hard-coded 3 second sleep in the balance widget flow. In withdraws.spec.ts, update the test case that navigates to the withdraw page to wait on a concrete readiness signal instead of waitForTimeout, using the existing page.locator checks around the balance widget or another stable loading-complete indicator so the test becomes faster and less flaky.
105-137: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAlign the
/withdraw-requestsfatal check with the other route test.tests/pw/tests/e2e/withdraws/withdraws.spec.ts:131still usesFatal error|Parse erroronly, so it can miss theThere has been a critical errorbanner that the adjacent route test already guards against.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/withdraws/withdraws.spec.ts` around lines 105 - 137, Update the `/withdraw-requests` check in `withdraws.spec.ts` to match the `/withdraw` test’s fatal-banner detection. In the `Test Case 2 - /withdraw-requests route mounts` block, extend the text matcher used with `page.locator(...).isVisible()` so it also catches `There has been a critical error`, keeping the assertion behavior consistent with the adjacent test.tests/pw/tests/e2e/new-withdraw/newWithdraw.spec.ts (1)
15-23: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winBounded 5-iteration cleanup may leave stale pending withdraws uncleared.
clearVendorPendingcancels one pending withdraw per loop iteration, capped at 5, then silently exits regardless of whether any pending withdraws remain. In a heavily-used/"polluted" test environment (a condition explicitly called out elsewhere in this test suite for other list endpoints), more than 5 leftover pending withdraws would leave the vendor account in a non-clean state beforeseedPendingWithdraw/seedApprovedWithdrawproceed to create a new one —createWithdrawdoes have a fallback for the "already pending" 400 case, but that fallback reuses/updates the existing stale withdraw rather than guaranteeing the freshly-requestedamount.♻️ Suggested fix — loop until actually clear (with a sane upper bound)
async function clearVendorPending(apiUtils: ApiUtils): Promise<void> { - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 20; i++) { const pending = await apiUtils.getAllWithdrawsByStatus('pending', payloads.vendorAuth); if (!Array.isArray(pending) || pending.length === 0) { return; } await apiUtils.cancelWithdraw(String(pending[0].id), payloads.adminAuth); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-withdraw/newWithdraw.spec.ts` around lines 15 - 23, clearVendorPending only cancels up to 5 pending withdraws and can exit while stale records still exist, which can leave the vendor state polluted before seedPendingWithdraw/seedApprovedWithdraw runs. Update clearVendorPending to keep checking getAllWithdrawsByStatus('pending', payloads.vendorAuth) and canceling until no pending withdraws remain, with a sane safety cap or guard to avoid infinite loops. Keep the fix localized to clearVendorPending and its use of ApiUtils.cancelWithdraw/getAllWithdrawsByStatus.
🧹 Nitpick comments (10)
tests/pw/utils/dataViews.ts (1)
79-107: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winDefault
rowSelectorforwaitForListReadycan report "ready" during loading.
waitForListReadydefaultsrowSelectortoDATA_ROW_ANY, which is documented elsewhere in this same file as "Any body row, INCLUDING skeleton placeholder rows shown while loading." Since the §5 contract is defined as "the list is ready when >=1 row OR an empty-state OR a surface-specific extra signal is present", a caller that doesn't overriderowSelectorwill resolve "ready" the instant skeleton placeholder rows paint — before real data settles. Existing page objects sidestep this by always passing a customrowSelector/extraReady, but the public default is a footgun for future new-UI folders that follow the documented API surface at face value.Consider defaulting to a settled-row selector (e.g.
DATA_ROW_SETTLED) or documenting inline that callers on skeleton-rendering surfaces MUST overriderowSelector.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/utils/dataViews.ts` around lines 79 - 107, The default row selector in waitForListReady is too permissive because DATA_ROW_ANY includes loading skeleton rows, so the helper can resolve before real data is settled. Update waitForListReady (and its ListReadyOpts rowSelector default) to use a settled-row selector such as DATA_ROW_SETTLED, or otherwise make the default explicitly exclude skeletons; keep DATA_ROW_ANY only for callers that intentionally override it. If you choose to keep the current default, add inline guidance in ListReadyOpts/waitForListReady that any skeleton-rendering surface must pass a custom rowSelector.tests/pw/tests/e2e/new-vendor-staff/newVendorStaff.spec.ts (1)
219-234: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueFixed
waitForTimeout(2500)before an already-retrying assertion.
expect(...).toBeVisible({ timeout: 15000 })on the next line already retries/polls, making the preceding fixed 2.5s sleep redundant and just adds constant latency to the run without improving reliability.♻️ Proposed fix
- await page.goto(staff.listUrl); - await page.waitForLoadState('domcontentloaded'); - await page.waitForTimeout(2500); - await expect(page.locator(newVendorStaffSelectors.permissionDenied), 'staff is denied the staff-management route').toBeVisible({ timeout: 15000 }); + await page.goto(staff.listUrl); + await page.waitForLoadState('domcontentloaded'); + await expect(page.locator(newVendorStaffSelectors.permissionDenied), 'staff is denied the staff-management route').toBeVisible({ timeout: 15000 });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-vendor-staff/newVendorStaff.spec.ts` around lines 219 - 234, Remove the redundant fixed wait in the staff-management access test and rely on the existing retrying assertion instead. In newVendorStaff.spec.ts, within the "staff cannot access the staff-management route (React)" test, delete the page.waitForTimeout(2500) call after page.waitForLoadState('domcontentloaded') and keep the expect(page.locator(newVendorStaffSelectors.permissionDenied)).toBeVisible({ timeout: 15000 }) check as the synchronization point.tests/pw/tests/e2e/new-store-support/newStoreSupportPage.ts (1)
259-266: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSilent fallback swallows selection failures.
If
selectOption({ label: 'Close Ticket' })fails and the hardcoded fallback value'1'no longer matches the option's underlying value (e.g. after a future markup change), the.catch(() => undefined)swallows the failure silently —submitReply(..., true)proceeds without actually flipping the status dropdown, and the caller only discovers this indirectly via a REST assertion failure downstream, obscuring the root cause.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-store-support/newStoreSupportPage.ts` around lines 259 - 266, The Close Ticket selection in submitReply is swallowing real failures because the fallback selectOption('1') is ignored if it also fails. Update the selection logic around submitReply and the replyStatusSelect locator so failures are surfaced instead of returning undefined; only fall back if you can verify the option, and otherwise throw or log a clear error so the caller knows the status change did not happen.tests/pw/tests/e2e/new-store-support/newStoreSupport.spec.ts (2)
154-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSpec reaches into the page object's
listRestselector as a REST base URL instead of usingendPoints.Other specs in this wave (e.g.
newProductQa.spec.ts) fetch REST verification data viaendPoints.*helpers. HerenewStoreSupportSelectors.listRest— defined for UIwaitForResponsematching in the page object — is reused as a direct REST call base (${SERVER_URL}${newStoreSupportSelectors.listRest}/${id}). This couples spec-level REST assertions to an internal UI selector; if that selector is later changed to a regex or a different route shape (matching the pattern already used elsewhere in this same file, e.g.replyRest/statusRest), these REST calls silently break.Consider adding/using a proper
endPointsentry for this route, consistent with the rest of the conversion wave.Also applies to: 164-165, 173-174, 182-183
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-store-support/newStoreSupport.spec.ts` around lines 154 - 155, The ticket verification is using the page object’s listRest UI selector as if it were a REST base URL, which makes the spec too tightly coupled to UI matching details. Update the affected assertions in newStoreSupport.spec.ts to use a proper endPoints helper for this route, matching the pattern used by newProductQa.spec.ts and the existing replyRest/statusRest usages, and keep listRest reserved for waitForResponse-style selector matching only.
58-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded REST cleanup path bypasses
endPoints/ApiUtilsabstraction.
/wp/v2/dokan_store_support/${id}?force=trueis a literal endpoint constructed inline rather than via a shared endpoint helper, unlike other cleanup flows in this cohort (e.g.newProductQa.spec.tsusesendPoints.updateBatchProductQuestions). Low risk since it's cleanup-only, but worth aligning for consistency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-store-support/newStoreSupport.spec.ts` around lines 58 - 64, The cleanup in test.afterAll is using a hardcoded REST URL instead of the shared endpoint abstraction. Update the delete call to use the existing endPoints/ApiUtils pattern used elsewhere in this suite so the dokan_store_support cleanup path is constructed consistently, and keep the logic inside test.afterAll and seededIds cleanup unchanged.tests/pw/tests/e2e/new-product-qa/newProductQa.spec.ts (1)
95-102: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueAvoid building a
RegExpfrom a variable for URL matching.
idis a REST-issued numeric string here so exploitation risk is minimal, but the static analyzer's regexp-from-variable concern is easy to avoid entirely by using a plain substring check instead of dynamic regex construction.🛡️ Proposed fix
- expect(page.url(), 'on the details route').toMatch(new RegExp(`#/product-questions-answers/${id}`)); + expect(page.url(), 'on the details route').toContain(`#/product-questions-answers/${id}`);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-product-qa/newProductQa.spec.ts` around lines 95 - 102, The URL assertion in the vendor question details test is building a RegExp from the seeded id, which the analyzer flags unnecessarily. In the test around openQuestionByText and the details-route check, replace the dynamic regex-based URL match with a plain substring or equivalent non-regex assertion that still verifies the route contains the question id.Source: Linters/SAST tools
tests/pw/tests/e2e/new-vendor-support/newVendorSupportPage.ts (1)
54-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSame duplicated actions-button literal as the return-request page object.
rowActionsBtn: ROW_ACTIONS_BTNis defined in the selectors object but bothrowActionItems(line 240) andcloseTicketFromRow(line 261) bypass it with a hardcoded string:await row.locator("button[aria-label='Actions']").first().click();♻️ Proposed fix
- await row.locator("button[aria-label='Actions']").first().click(); + await row.locator(newVendorSupportSelectors.rowActionsBtn).first().click();Apply to both occurrences (lines 240 and 261).
Also applies to: 237-255, 258-275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-vendor-support/newVendorSupportPage.ts` at line 54, The newVendorSupportPage object is bypassing the existing rowActionsBtn selector by hardcoding the Actions button locator in both rowActionItems and closeTicketFromRow. Update both flows to use the shared ROW_ACTIONS_BTN selector from the selectors object, so the page object has a single source of truth for the row actions button and stays consistent with the return-request page object.tests/pw/tests/e2e/new-return-request/newReturnRequestPage.ts (1)
33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded actions-button selector duplicates the already-defined
rowActionsBtnconstant.The selectors object exports
rowActionsBtn: ROW_ACTIONS_BTN(line 33), butdeleteRequestuses a hardcoded literal instead:await row.locator("button[aria-label='Actions']").first().click();This creates two sources of truth for the same selector; ifROW_ACTIONS_BTNever changes in@utils/dataViews, this call site silently drifts out of sync.♻️ Proposed fix
- await row.locator("button[aria-label='Actions']").first().click(); + await row.locator(newReturnRequestSelectors.rowActionsBtn).first().click();Also applies to: 263-269
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-return-request/newReturnRequestPage.ts` at line 33, The `deleteRequest` flow is using a hardcoded Actions button selector instead of the shared `ROW_ACTIONS_BTN`/`rowActionsBtn` constant defined in the selectors object, creating duplicate sources of truth. Update the locator in `deleteRequest` (and any other affected call sites in this page object) to use the existing selector constant or the selectors map so all Actions-button references stay aligned if `@utils/dataViews` changes.tests/pw/tests/e2e/product-qa/productQA.spec.ts (1)
177-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a reporter-visible skip reason.
test.describe.skip('...')doesn't surface the retirement rationale in HTML/JUnit reports — only the inline comment explains it. Playwright supportstest.skip(true, 'reason')as the first statement inside an (unskipped) describe to get the same skip behavior with the reason shown in the report.♻️ Optional pattern for reporter-visible skip reason
-test.describe.skip('Product Q&A (React) Tests `@pro`', () => { +test.describe('Product Q&A (React) Tests `@pro`', () => { + test.skip(true, 'Retired (D1/D4): superseded by tests/e2e/new-product-qa/');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/product-qa/productQA.spec.ts` around lines 177 - 180, The Product Q&A React suite is being skipped without exposing the retirement rationale in test reports. Update the `test.describe.skip('Product Q&A (React) Tests `@pro`', ...)` block in `productQA.spec.ts` to an unskipped `test.describe(...)` and add a reporter-visible `test.skip(true, '...')` as the first statement inside that describe so the reason appears in HTML/JUnit output while preserving the skip behavior.tests/pw/tests/e2e/new-seller-badge/newSellerBadgePage.ts (1)
75-83: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueSequential per-tab wait can add up to ~15s on the failure path.
tabsVisible()now waits up to 5s per tab sequentially instead of reading all three immediately; if tabs genuinely fail to render, the check takes up to 15s before returningfalse. This is a reasonable flakiness fix given the documented rationale (search re-render detaches tab strip), but considerPromise.allover[...].map(tab => tab.waitFor(...).then(...).catch(...))to keep the resilience while bounding worst-case latency to ~5s instead of ~15s.♻️ Optional parallelization
- async tabsVisible(): Promise<boolean> { - for (const tab of [this.allTab, this.myBadgesTab, this.availableBadgesTab]) { - const ok = await tab - .waitFor({ state: 'visible', timeout: 5000 }) - .then(() => true) - .catch(() => false); - if (!ok) return false; - } - return true; - } + async tabsVisible(): Promise<boolean> { + const results = await Promise.all( + [this.allTab, this.myBadgesTab, this.availableBadgesTab].map((tab) => + tab.waitFor({ state: 'visible', timeout: 5000 }).then(() => true).catch(() => false), + ), + ); + return results.every(Boolean); + }Also applies to: 103-112
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-seller-badge/newSellerBadgePage.ts` around lines 75 - 83, The tabsVisible() check is waiting on each tab sequentially, which can stretch the failure path to about 15s when tabs never appear. Update tabsVisible() to wait for all tabs in parallel using Promise.all over the tab wait calls while preserving the current detach/re-render resilience, and keep the existing waitForReady() flow in NewSellerBadgePage unchanged except for relying on the faster tabsVisible() behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/pw/tests/e2e/new-withdraw/newWithdrawPage.ts`:
- Around line 143-159: The withdraw page readiness and row-count logic is
treating loading skeleton rows as real data rows, so the list can resolve too
early. Update newWithdrawPage’s waitForListReady(), getRowCount(), and
rawRowCount() to use the settled-row pattern from the orders/reviews pages: wait
for the table to settle before counting, and exclude skeleton <tr> elements from
the row selector or filtering logic. Keep the fix anchored around
newWithdrawSelectors.dataRow and the list-ready helpers so readiness is only
reported after the list is genuinely loaded.
In `@tests/pw/utils/dataViews.ts`:
- Around line 117-143: waitForDataViewsSettle is mixing a configurable timeout
with a hardcoded 5s polling cap, so the final settle loop can exit too early on
slow pages. Update the inner poll in waitForDataViewsSettle to use the same
timeout source as the earlier waits (opts.timeout) instead of a fixed 5000ms,
and keep the existing row/empty-state checks unchanged so callers like
applyAndValidateDataViewsFilter get consistent behavior.
---
Outside diff comments:
In `@tests/pw/tests/e2e/new-withdraw/newWithdraw.spec.ts`:
- Around line 15-23: clearVendorPending only cancels up to 5 pending withdraws
and can exit while stale records still exist, which can leave the vendor state
polluted before seedPendingWithdraw/seedApprovedWithdraw runs. Update
clearVendorPending to keep checking getAllWithdrawsByStatus('pending',
payloads.vendorAuth) and canceling until no pending withdraws remain, with a
sane safety cap or guard to avoid infinite loops. Keep the fix localized to
clearVendorPending and its use of
ApiUtils.cancelWithdraw/getAllWithdrawsByStatus.
In `@tests/pw/tests/e2e/withdraws/withdraws.spec.ts`:
- Around line 138-155: The withdraw page test is using a hard-coded 3 second
sleep in the balance widget flow. In withdraws.spec.ts, update the test case
that navigates to the withdraw page to wait on a concrete readiness signal
instead of waitForTimeout, using the existing page.locator checks around the
balance widget or another stable loading-complete indicator so the test becomes
faster and less flaky.
- Around line 105-137: Update the `/withdraw-requests` check in
`withdraws.spec.ts` to match the `/withdraw` test’s fatal-banner detection. In
the `Test Case 2 - /withdraw-requests route mounts` block, extend the text
matcher used with `page.locator(...).isVisible()` so it also catches `There has
been a critical error`, keeping the assertion behavior consistent with the
adjacent test.
---
Nitpick comments:
In `@tests/pw/tests/e2e/new-product-qa/newProductQa.spec.ts`:
- Around line 95-102: The URL assertion in the vendor question details test is
building a RegExp from the seeded id, which the analyzer flags unnecessarily. In
the test around openQuestionByText and the details-route check, replace the
dynamic regex-based URL match with a plain substring or equivalent non-regex
assertion that still verifies the route contains the question id.
In `@tests/pw/tests/e2e/new-return-request/newReturnRequestPage.ts`:
- Line 33: The `deleteRequest` flow is using a hardcoded Actions button selector
instead of the shared `ROW_ACTIONS_BTN`/`rowActionsBtn` constant defined in the
selectors object, creating duplicate sources of truth. Update the locator in
`deleteRequest` (and any other affected call sites in this page object) to use
the existing selector constant or the selectors map so all Actions-button
references stay aligned if `@utils/dataViews` changes.
In `@tests/pw/tests/e2e/new-seller-badge/newSellerBadgePage.ts`:
- Around line 75-83: The tabsVisible() check is waiting on each tab
sequentially, which can stretch the failure path to about 15s when tabs never
appear. Update tabsVisible() to wait for all tabs in parallel using Promise.all
over the tab wait calls while preserving the current detach/re-render
resilience, and keep the existing waitForReady() flow in NewSellerBadgePage
unchanged except for relying on the faster tabsVisible() behavior.
In `@tests/pw/tests/e2e/new-store-support/newStoreSupport.spec.ts`:
- Around line 154-155: The ticket verification is using the page object’s
listRest UI selector as if it were a REST base URL, which makes the spec too
tightly coupled to UI matching details. Update the affected assertions in
newStoreSupport.spec.ts to use a proper endPoints helper for this route,
matching the pattern used by newProductQa.spec.ts and the existing
replyRest/statusRest usages, and keep listRest reserved for
waitForResponse-style selector matching only.
- Around line 58-64: The cleanup in test.afterAll is using a hardcoded REST URL
instead of the shared endpoint abstraction. Update the delete call to use the
existing endPoints/ApiUtils pattern used elsewhere in this suite so the
dokan_store_support cleanup path is constructed consistently, and keep the logic
inside test.afterAll and seededIds cleanup unchanged.
In `@tests/pw/tests/e2e/new-store-support/newStoreSupportPage.ts`:
- Around line 259-266: The Close Ticket selection in submitReply is swallowing
real failures because the fallback selectOption('1') is ignored if it also
fails. Update the selection logic around submitReply and the replyStatusSelect
locator so failures are surfaced instead of returning undefined; only fall back
if you can verify the option, and otherwise throw or log a clear error so the
caller knows the status change did not happen.
In `@tests/pw/tests/e2e/new-vendor-staff/newVendorStaff.spec.ts`:
- Around line 219-234: Remove the redundant fixed wait in the staff-management
access test and rely on the existing retrying assertion instead. In
newVendorStaff.spec.ts, within the "staff cannot access the staff-management
route (React)" test, delete the page.waitForTimeout(2500) call after
page.waitForLoadState('domcontentloaded') and keep the
expect(page.locator(newVendorStaffSelectors.permissionDenied)).toBeVisible({
timeout: 15000 }) check as the synchronization point.
In `@tests/pw/tests/e2e/new-vendor-support/newVendorSupportPage.ts`:
- Line 54: The newVendorSupportPage object is bypassing the existing
rowActionsBtn selector by hardcoding the Actions button locator in both
rowActionItems and closeTicketFromRow. Update both flows to use the shared
ROW_ACTIONS_BTN selector from the selectors object, so the page object has a
single source of truth for the row actions button and stays consistent with the
return-request page object.
In `@tests/pw/tests/e2e/product-qa/productQA.spec.ts`:
- Around line 177-180: The Product Q&A React suite is being skipped without
exposing the retirement rationale in test reports. Update the
`test.describe.skip('Product Q&A (React) Tests `@pro`', ...)` block in
`productQA.spec.ts` to an unskipped `test.describe(...)` and add a
reporter-visible `test.skip(true, '...')` as the first statement inside that
describe so the reason appears in HTML/JUnit output while preserving the skip
behavior.
In `@tests/pw/utils/dataViews.ts`:
- Around line 79-107: The default row selector in waitForListReady is too
permissive because DATA_ROW_ANY includes loading skeleton rows, so the helper
can resolve before real data is settled. Update waitForListReady (and its
ListReadyOpts rowSelector default) to use a settled-row selector such as
DATA_ROW_SETTLED, or otherwise make the default explicitly exclude skeletons;
keep DATA_ROW_ANY only for callers that intentionally override it. If you choose
to keep the current default, add inline guidance in
ListReadyOpts/waitForListReady that any skeleton-rendering surface must pass a
custom rowSelector.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 62ddf34c-540f-4d3c-a230-74e12d7fef37
📒 Files selected for processing (59)
tests/pw/CONVERSION-LOG.mdtests/pw/feature-map/feature-map.ymltests/pw/package.jsontests/pw/tests/e2e/NEW_UI_HOUSE_STYLE.mdtests/pw/tests/e2e/admin/adminDataViews.tstests/pw/tests/e2e/announcements/announcements.spec.tstests/pw/tests/e2e/announcements/announcementsNewUI.spec.tstests/pw/tests/e2e/dashboard/dashboard.spec.tstests/pw/tests/e2e/dashboard/newVendorDashboardPage.tstests/pw/tests/e2e/intelligence/intelligence.spec.tstests/pw/tests/e2e/new-coupons/newCoupons.spec.tstests/pw/tests/e2e/new-manual-order/newManualOrder.spec.tstests/pw/tests/e2e/new-orders/newOrders.spec.tstests/pw/tests/e2e/new-orders/newOrdersPage.tstests/pw/tests/e2e/new-product-qa/newProductQa.spec.tstests/pw/tests/e2e/new-product-qa/newProductQaPage.tstests/pw/tests/e2e/new-products/bugs/status-tab-flakiness.mdtests/pw/tests/e2e/new-products/newProducts.spec.tstests/pw/tests/e2e/new-return-request/newReturnRequest.spec.tstests/pw/tests/e2e/new-return-request/newReturnRequestPage.tstests/pw/tests/e2e/new-reverse-withdraw/newReverseWithdraw.spec.tstests/pw/tests/e2e/new-seller-badge/newSellerBadge.spec.tstests/pw/tests/e2e/new-seller-badge/newSellerBadgePage.tstests/pw/tests/e2e/new-shipping/newShipping.spec.tstests/pw/tests/e2e/new-social/newSocial.spec.tstests/pw/tests/e2e/new-store-reviews/newStoreReviews.spec.tstests/pw/tests/e2e/new-store-reviews/newStoreReviewsPage.tstests/pw/tests/e2e/new-store-seo/newStoreSeo.spec.tstests/pw/tests/e2e/new-store-support/newStoreSupport.spec.tstests/pw/tests/e2e/new-store-support/newStoreSupportPage.tstests/pw/tests/e2e/new-vendor-staff/newVendorStaff.spec.tstests/pw/tests/e2e/new-vendor-staff/newVendorStaffPage.tstests/pw/tests/e2e/new-vendor-support/newVendorSupport.spec.tstests/pw/tests/e2e/new-vendor-support/newVendorSupportPage.tstests/pw/tests/e2e/new-vendor-verifications/newVendorVerifications.spec.tstests/pw/tests/e2e/new-vendor-verifications/newVendorVerificationsPage.tstests/pw/tests/e2e/new-withdraw/newWithdraw.spec.tstests/pw/tests/e2e/new-withdraw/newWithdrawPage.tstests/pw/tests/e2e/orders/newOrderListPage.tstests/pw/tests/e2e/orders/orders.spec.tstests/pw/tests/e2e/product-form-manager/newProductForm.spec.tstests/pw/tests/e2e/product-form-manager/newProductFormAdvanced.spec.tstests/pw/tests/e2e/product-form-manager/newProductFormValidation.spec.tstests/pw/tests/e2e/product-qa/productQA.spec.tstests/pw/tests/e2e/product-variations/productVariations.spec.tstests/pw/tests/e2e/reverse-withdraws/reverseWithdraws.spec.tstests/pw/tests/e2e/social-linking/socialLinking.spec.tstests/pw/tests/e2e/store-seo/storeSeo.spec.tstests/pw/tests/e2e/store-supports/storeSupports.spec.tstests/pw/tests/e2e/vendor-product-subscription/vendorProductSubscription.spec.tstests/pw/tests/e2e/vendor-products/addProduct.spec.tstests/pw/tests/e2e/vendor-products/newProductListPage.tstests/pw/tests/e2e/vendor-return-request/vendorReturnRequest.spec.tstests/pw/tests/e2e/vendor-staff/vendorStaff.spec.tstests/pw/tests/e2e/vendor-subscriptions/vendorSubscriptions.spec.tstests/pw/tests/e2e/vendor-verifications/vendorVerifications.spec.tstests/pw/tests/e2e/withdraws/withdraws.spec.tstests/pw/utils/authStates.tstests/pw/utils/dataViews.ts
| async waitForRootReady(timeoutMs = 30000): Promise<void> { | ||
| await this.page.locator(newWithdrawSelectors.reactRoot).first().waitFor({ state: 'visible', timeout: timeoutMs }); | ||
| await dvWaitForRootReady(this.page, timeoutMs); | ||
| } | ||
|
|
||
| /** List is ready when the react root is visible AND (>=1 row OR an empty | ||
| * state OR the status tabs) is present — poll up to ~15s. */ | ||
| * state OR the status tabs) is present — poll up to ~15s (house-style §5). */ | ||
| async waitForListReady(): Promise<void> { | ||
| const start = Date.now(); | ||
| while (Date.now() - start < 15000) { | ||
| const rows = await this.page.locator(newWithdrawSelectors.dataRow).count(); | ||
| if (rows > 0) return; | ||
| const empty = await this.page.locator(newWithdrawSelectors.emptyState).count(); | ||
| if (empty > 0) return; | ||
| const tabs = await this.tabPending.isVisible().catch(() => false); | ||
| if (tabs) return; | ||
| await this.page.waitForTimeout(250); | ||
| } | ||
| await dvWaitForListReady(this.page, { | ||
| rowSelector: newWithdrawSelectors.dataRow, | ||
| emptyState: newWithdrawSelectors.emptyState, | ||
| extraReady: () => this.tabPending.isVisible(), | ||
| }); | ||
| } | ||
|
|
||
| async hasNoPhpFatal(): Promise<boolean> { | ||
| const fatal = await this.page.locator(newWithdrawSelectors.phpFatal).first().isVisible({ timeout: 1000 }).catch(() => false); | ||
| return !fatal; | ||
| return await dvHasNoPhpFatal(this.page); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the full dataViews.ts definitions used here and check for a settled-row variant reused by withdraw.
fd dataViews.ts tests/pw/utils --exec cat -n {}Repository: getdokan/dokan
Length of output: 20009
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the withdraw page object, selector definitions, and comparable page objects.
for f in \
tests/pw/tests/e2e/new-withdraw/newWithdrawPage.ts \
tests/pw/tests/e2e/new-withdraw/newWithdrawSelectors.ts \
tests/pw/tests/e2e/new-orders/newOrdersPage.ts \
tests/pw/tests/e2e/new-store-reviews/newStoreReviewsPage.ts
do
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
echo
doneRepository: getdokan/dokan
Length of output: 15738
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "===== new-orders page object ====="
wc -l tests/pw/tests/e2e/new-orders/newOrdersPage.ts
cat -n tests/pw/tests/e2e/new-orders/newOrdersPage.ts | sed -n '1,260p'
echo
echo "===== new-store-reviews page object ====="
wc -l tests/pw/tests/e2e/new-store-reviews/newStoreReviewsPage.ts
cat -n tests/pw/tests/e2e/new-store-reviews/newStoreReviewsPage.ts | sed -n '1,260p'
echo
echo "===== withdraw folder skeleton/data-row references ====="
rg -n --hidden --glob 'tests/pw/tests/e2e/new-withdraw/**' --glob '!**/node_modules/**' 'data-slot="skeleton"|animate-pulse|rawRowCount\(|waitForListReady\(|dataRowSettled|DATA_ROW_SETTLED|countRealRows|waitForListSettled'Repository: getdokan/dokan
Length of output: 27870
Exclude skeleton rows before resolving the withdraw list
waitForListReady() and getRowCount() still use newWithdrawSelectors.dataRow, which matches loading skeleton <tr>s too. rawRowCount() also does no settle step. Because extraReady: () => this.tabPending.isVisible() can become true immediately, this page can report ready and return a row count while the table is still loading/refetching. Use the settled-row pattern from orders/reviews here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/new-withdraw/newWithdrawPage.ts` around lines 143 - 159,
The withdraw page readiness and row-count logic is treating loading skeleton
rows as real data rows, so the list can resolve too early. Update
newWithdrawPage’s waitForListReady(), getRowCount(), and rawRowCount() to use
the settled-row pattern from the orders/reviews pages: wait for the table to
settle before counting, and exclude skeleton <tr> elements from the row selector
or filtering logic. Keep the fix anchored around newWithdrawSelectors.dataRow
and the list-ready helpers so readiness is only reported after the list is
genuinely loaded.
| export async function waitForDataViewsSettle(page: Page, opts: { timeout?: number; debounceMs?: number } = {}): Promise<void> { | ||
| const timeout = opts.timeout ?? 10000; | ||
| const debounceMs = opts.debounceMs ?? 350; | ||
|
|
||
| await page.waitForTimeout(debounceMs); // let a debounced search/tab request dispatch. | ||
| await page.waitForLoadState('networkidle', { timeout }).catch(() => undefined); | ||
|
|
||
| // Wait out the loading skeleton so its <tr>s aren't read as fresh data. | ||
| await page | ||
| .locator(SKELETON) | ||
| .first() | ||
| .waitFor({ state: 'hidden', timeout }) | ||
| .catch(() => undefined); | ||
|
|
||
| const rows = page.locator(DATA_ROW); | ||
| const empty = page.getByText(/no data found/i).first(); | ||
| const start = Date.now(); | ||
| while (Date.now() - start < 5000) { | ||
| if (await empty.isVisible({ timeout: 250 }).catch(() => false)) { | ||
| return; // terminal empty state painted. | ||
| } | ||
| if ((await rows.count()) > 0) { | ||
| return; // fresh rows painted. | ||
| } | ||
| await page.waitForTimeout(150); | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
waitForDataViewsSettle's inner poll ignores the configurable timeout.
The function accepts opts.timeout and uses it for waitForLoadState and the skeleton-hidden wait (Lines 122, 128), but the final settle poll at Line 134 hardcodes while (Date.now() - start < 5000) regardless of opts.timeout. A caller passing a longer timeout for a slow surface (e.g. applyAndValidateDataViewsFilter, which calls this with only debounceMs overridden) still gets capped at 5s for the final settle check, which can silently return without ever seeing fresh rows on slower environments.
🔧 Proposed fix
- const start = Date.now();
- while (Date.now() - start < 5000) {
+ const start = Date.now();
+ while (Date.now() - start < timeout) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function waitForDataViewsSettle(page: Page, opts: { timeout?: number; debounceMs?: number } = {}): Promise<void> { | |
| const timeout = opts.timeout ?? 10000; | |
| const debounceMs = opts.debounceMs ?? 350; | |
| await page.waitForTimeout(debounceMs); // let a debounced search/tab request dispatch. | |
| await page.waitForLoadState('networkidle', { timeout }).catch(() => undefined); | |
| // Wait out the loading skeleton so its <tr>s aren't read as fresh data. | |
| await page | |
| .locator(SKELETON) | |
| .first() | |
| .waitFor({ state: 'hidden', timeout }) | |
| .catch(() => undefined); | |
| const rows = page.locator(DATA_ROW); | |
| const empty = page.getByText(/no data found/i).first(); | |
| const start = Date.now(); | |
| while (Date.now() - start < 5000) { | |
| if (await empty.isVisible({ timeout: 250 }).catch(() => false)) { | |
| return; // terminal empty state painted. | |
| } | |
| if ((await rows.count()) > 0) { | |
| return; // fresh rows painted. | |
| } | |
| await page.waitForTimeout(150); | |
| } | |
| } | |
| export async function waitForDataViewsSettle(page: Page, opts: { timeout?: number; debounceMs?: number } = {}): Promise<void> { | |
| const timeout = opts.timeout ?? 10000; | |
| const debounceMs = opts.debounceMs ?? 350; | |
| await page.waitForTimeout(debounceMs); // let a debounced search/tab request dispatch. | |
| await page.waitForLoadState('networkidle', { timeout }).catch(() => undefined); | |
| // Wait out the loading skeleton so its <tr>s aren't read as fresh data. | |
| await page | |
| .locator(SKELETON) | |
| .first() | |
| .waitFor({ state: 'hidden', timeout }) | |
| .catch(() => undefined); | |
| const rows = page.locator(DATA_ROW); | |
| const empty = page.getByText(/no data found/i).first(); | |
| const start = Date.now(); | |
| while (Date.now() - start < timeout) { | |
| if (await empty.isVisible({ timeout: 250 }).catch(() => false)) { | |
| return; // terminal empty state painted. | |
| } | |
| if ((await rows.count()) > 0) { | |
| return; // fresh rows painted. | |
| } | |
| await page.waitForTimeout(150); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/utils/dataViews.ts` around lines 117 - 143, waitForDataViewsSettle
is mixing a configurable timeout with a hardcoded 5s polling cap, so the final
settle loop can exit too early on slow pages. Update the inner poll in
waitForDataViewsSettle to use the same timeout source as the earlier waits
(opts.timeout) instead of a fixed 5000ms, and keep the existing row/empty-state
checks unchanged so callers like applyAndValidateDataViewsFilter get consistent
behavior.
…, green 3×
Flagship Wave 2 item: real edit-persistence coverage for the React product
editor at /dashboard/new/#/products/:id/edit, replacing the legacy
products-details spec (95 all-vendor cases driven through a no-op STUB — vacuous
green).
D2 consolidation:
- Moved product-form-manager/newProductForm{Page,,.Validation,Advanced} into a
new tests/e2e/new-product-form/ folder (create + edit now co-located). The
wp-admin productFormManagerAdmin* files stay put (different SPA, @admin).
- Extended newProductFormPage.ts with editUrl/gotoEdit/waitForEditReady/saveEdit
(no-redirect save: PUT-response + "Product saved successfully." toast, never a
URL race — edit does not redirect) + read-back getters.
newProductFormEdit.spec.ts — 10 edit tests, green 3x (2 headless + 1 headed),
single-worker against Docker :9999. Each mutates a field, clicks Update Product,
and asserts persistence via getSingleProduct (REST) + reload: title, price,
discount sale price, sale>regular validation (negative — no PUT), short+long
descriptions, virtual, SKU, catalog visibility, category change, feature image
(wp.media).
Live learnings: the seed MUST carry a regular_price (the editor's Update button
is disabled while the form is invalid — a priceless seed blocked every non-price
edit); the editor is heavy (~37s/test) and the wp-env degrades under
parallel/headed load, so run single-worker.
Backlog (documented in CONVERSION-LOG): manage-stock/stock-status/tags don't
persist through their dynamic react-select interactions on the edit surface
(need live selector work) + the remaining ~80 legacy field groups.
Legacy retired: products-details/productsDetails.spec.ts whole describe (all
stub, all-vendor) -> test.describe.skip with a pointer (D3-safe). feature-map:
10 new-UI edit leaves under the Products vendor (new UI) group.
Scout findings recorded for the rest of Wave 2 (not built): B20 refunds has NO
React surface (stays legacy, D4); C2 order-edit reuses the manual-order editor
already covered by new-manual-order/; B17 product-create gaps mapped for a later
batch.
…B17), green 3× Extends the product coverage with the Pro-only list actions and a create-type gap the existing new-products / product-form specs don't exercise. - new-products/newProductsProActions.spec.ts (3 tests, green 3x): Pro row actions offered (Quick Edit / Duplicate); duplicate from the row menu (gated on POST /dokan/v2/products/:id/duplicate, REST oracle: 2 rows share the name); quick-edit a price (DokanModal "Quick Edit Product" -> Update Product -> POST /dokan/v3/products/batch -> REST getSingleProduct confirms the new price). - new-product-form/newProductFormTypes.spec.ts (1 test, green 3x): create a virtual product (full create + virtual===true via REST) — the legacy virtual motive, previously only a toggle assertion. Live limitations found + documented (deferred, NOT faked): - The React editor lists External/Affiliate + Group Product in the type dropdown but does NOT render their type-specific fields (external_url / button_text / grouped_products wrappers absent after choosing the type) — external/grouped CREATE can't be driven from the form yet (a real editor gap). - The editor requires a non-empty description before Save enables. - Variable (two-step), subscription (module-gated), downloadable-with-file, and the D3 admin-split of products.spec.ts remain backlog (in CONVERSION-LOG). feature-map: 4 new-UI leaves added to the Products vendor (new UI) group.
Extends the React vendor Orders list coverage (new-orders/) with the two
behavioral gaps it left, on the lighter list surface.
- new-orders/newOrdersGaps.spec.ts (2 tests, green 3x):
* status-change PERSISTS: drive a row's "Change status to completed" through
the confirm dialog, then REST getSingleOrder(id).status === 'completed'
(the existing new-orders test only asserted a request fired).
* MONEY ORACLE: vendor earning + admin commission (per line-item, vendor +
admin REST) reconciles to the order's product revenue — the house-style
money oracle, asserted nowhere in new-orders before.
Deferred (documented, not faked): a real CSV export-download test — the React
export is a hidden-form POST to a nonce'd URL and headless Chromium does not
fire a `download` event for it; customer-filter + date-range row-correctness,
and C2 order-edit (needs a new /dokan/v1/manual-orders seeder + wp-cli gate +
the heavy manual-order editor) remain backlog.
feature-map: 2 new-UI leaves under the Orders vendor (new UI) group.
…followers, shipstation, add-ons, announcements, seller-badge, user-subscription, subscription, advertising, delivery-time) Convert the vendor Pro-module e2e suites to the React vendor dashboard (/dashboard/new/#/<route>). 12 self-contained new-* folders, ~75 tests, each green per-folder and validated collectively (158 passed / 3 skipped / 0 failed). New folders: new-followers, new-shipstation, new-requested-quotes, new-product-addons, new-announcements, new-auction, new-booking, new-user-subscription, new-subscription, new-product-advertising, new-delivery-time; new-seller-badge extended (+2 gap tests). Behavioral oracles are REST/DB-first (row appears/disappears, status persists, value round-trips), never toast text. Legacy retirement applied: the D4 "(React)" smokes (which navigate legacy URLs) are describe.skip'd and the ported vendor cases are test.skip'd — only where equivalent new coverage exists (auction duplicate/activity-filter kept; no bid seeder). Findings: - Product bug filed: new-followers/bugs/follow-store-followers-cache-stale.md (unfollow cache-key mismatch leaves stale followers ~2wk; test is test.fixme). - WC-Subscriptions seeder works (POST /wc/v3/subscriptions + _dokan_vendor_id). - Subscription pack seeder still blocked (documented skip, as the legacy suite); advertising Promote global IS localized on the new dashboard (no bug). Deferred within Wave 3 (documented in CONVERSION-LOG): booking my-bookings + Confirm (no booking-row seeder), subscription cancel/active-pack (assign mints a new store), auction activity bid tests (no bid seeder).
…23), order-status gate (B26)
Long-tail conversion of the clean, high-confidence Wave 4 items (the rest are
recorded stays-legacy or documented deferrals). All green.
- F7 menu-manager: net-new new-menu-manager/ — the React vendor sidebar honors
dokan_menu_manager rename / hide / reorder (DB-seeded, asserted on the sidebar,
restored in afterAll). Gotcha: is_switched_on runs through wc_string_to_bool,
so 'on' evaluates false — use 'yes'/'no'.
- B23 wholesale: extend new-product-form/ — a create-surface persistence test
(REST _dokan_wholesale_meta == {enable_wholesale, price, quantity}) + an
edit-surface hydration test. R-B23-1 disproved (price/qty DO persist); the
shared enableWholesale() selectors are stale (base-ui auto-ids) — targeted by
field label locally.
- B26 order-status-change gate: extend new-orders/newOrdersGaps.spec.ts — the
negative capability path (order_status_change='off' → server rejects the
vendor status change → REST status unchanged). Serial; seeds before toggling.
Recorded stays-legacy: B22 EU (legacy form hooks, no editor schema), B27/B28
stripe (Payment settings legacy; stripe branch scope respected), B16 RW notices
(PHP template). Deferred (buildable, seeder-risky — briefs in the session):
B15-a/b withdraw schedule + make-default, B16-4/6 RW date-filter + pay-now,
B30/B31 shipping table-rate/distance-rate drill-ins.
…bursement schedule widget) Extend new-withdraw/ with the two vendor flows the legacy withdraws.spec.ts (no-op stub) never really covered: - B15-b: set a configured method as the default withdraw method — React "Make Default" fires POST /dokan/v2/withdraw/make-default-method and the REST withdraw_method flips (gracefully skips if only one active method). - B15-a: the auto-withdraw disbursement Schedule widget renders on /withdraw when admin disbursement is enabled (dokan_withdraw seed; REST enabled:true precheck). Seeds paypal + a fully-configured bank as active_methods. Behavioral oracles are the REST make-default flip and the disbursement REST — never toast text. Remaining Wave 4 deferrals (briefs ready; env-limited iteration): B16-4/6 reverse-withdraw date-filter + pay-now, B30/B31 shipping table/distance-rate drill-in forms. Recorded in CONVERSION-LOG.
shard-durations.json (generated 2026-07-01) was missing every new-* spec added in Waves 1-4 (27 specs), so getShardSpecs.js was mean-defaulting them at runtime. Register them with count-scaled estimates (heavy product-editor specs ~22s/test, list/DataViews specs ~6s/test) so the committed baseline is complete. Verified: greedy longest-first bin-pack into 12 bins yields a per-shard spread of 0.3% (min 1010s / max 1013s / avg 1011s) — well within the ~10% target. Exact durations refresh on the next full CI run via specDurationReporter.
Add 82 `vendor (new UI)` leaves (byte-exact test titles) across 14 module pages for the Wave 3-4 conversions: Follow Store, ShipStation, Request for Quotation, Product Addon, Announcements (merged), Auction, Seller Badge (merged), Product Subscription, Vendor Subscription, Product Advertising, Delivery Time, MenuManager, Product Form Manager (wholesale), WooCommerce Booking. Titles with #/:/' are single-quoted per convention; the follow-store unfollow fixme (product cache bug) and the subscription pack-listing documented-skip are recorded false. YAML validated with prettier.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (11)
tests/pw/tests/e2e/announcements/announcements.spec.ts (1)
483-539: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTag additions inside a skipped describe block are currently no-ops.
The
@new-uitags added to Test Cases 1-4 (lines 507, 524, 539) live inside thetest.describe.skip(...)block started at line 483, so they have no effect on test selection until/unless the block is un-skipped. Likely intentional metadata upkeep ahead of full retirement, but flagging in case the intent was to actually run these under@new-uifiltering.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/announcements/announcements.spec.ts` around lines 483 - 539, The added `@new-ui` tags in the New Vendor Announcement test cases are currently ineffective because the entire block is wrapped in test.describe.skip, so they won’t affect selection unless the suite is unskipped. If the intent is to keep metadata only, no code change is needed; otherwise, move the tag updates into an active describe/test structure in announcements.spec.ts using the existing test.describe.skip and individual test declarations as the reference points.tests/pw/feature-map/feature-map.yml (1)
785-801: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCross-role business flow miscategorized under
vendor (new UI).Line 799's coupon-usage cross-role scenario ("vendor creates a coupon ... customer order applies it ... vendor sees usage increment") mixes a customer-driven flow directly into the
vendor (new UI):map, whereas equivalent scenarios elsewhere use a dedicatedbusiness flow (new UI):(Product Q&A, line 1435) orcross-role (new UI):(Return and Warranty Request line 1624, Vendor Verification line 2021) category. Doesn't break the coverage script (which recurses regardless of category name), but is inconsistent with the established convention in this same file.♻️ Suggested categorization fix
vendor (new UI): ... - vendor creates a coupon (React) -> customer order applies it -> vendor sees usage increment (React): true + cross-role (new UI): + vendor creates a coupon (React) -> customer order applies it -> vendor sees usage increment (React): true🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/feature-map/feature-map.yml` around lines 785 - 801, Move the mixed vendor/customer coupon-usage scenario out of the vendor (new UI) group and into the appropriate cross-role category used elsewhere in feature-map.yml, such as business flow (new UI) or cross-role (new UI). Keep the scenario text unchanged, but relocate the unique coupon usage entry so the grouping matches the established convention alongside the related vendor and customer sections.tests/pw/tests/e2e/new-seller-badge/newSellerBadge.spec.ts (1)
188-199: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winDuplicate cleanup query + silently swallowed DB errors.
The same
DELETE FROM ${DB_PREFIX}_dokan_seller_badge_acquired WHERE vendor_id = ?is repeated three times (Lines 197, 203, 227), and every cleanup call uses.catch(() => undefined). IfDB_PREFIX/VENDOR_IDare unset or the query fails for another reason, cleanup silently no-ops — stale acquired rows can leak into subsequent runs and cause flaky failures far from the actual root cause, while the rawINSERTat Line 233 (no.catch) would throw loudly for the same misconfiguration. Consider extracting a small helper for this query and at least logging (not fully swallowing) failures so misconfiguration surfaces immediately instead of manifesting as unrelated test flakiness later.♻️ Suggested helper
+async function deleteAcquiredRowsForVendor(): Promise<void> { + try { + await dbUtils.dbQuery(`DELETE FROM ${DB_PREFIX}_dokan_seller_badge_acquired WHERE vendor_id = ?`, [VENDOR_ID]); + } catch (err) { + console.warn('[gaps] failed to clean acquired badge rows:', err); + } +}Also applies to: 203-203, 227-227
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-seller-badge/newSellerBadge.spec.ts` around lines 188 - 199, The cleanup logic in newSellerBadge.spec.ts repeats the same acquired-rows DELETE and currently hides every DB failure with catch(() => undefined), which can mask bad DB_PREFIX/VENDOR_ID values or real query errors. Extract the repeated DELETE FROM ${DB_PREFIX}_dokan_seller_badge_acquired WHERE vendor_id = ? into a small helper used by the affected test hooks, and change the cleanup path in test.afterAll (and the other matching cleanup sites) to report failures with context instead of silently swallowing them. Keep the helper close to the existing apiUtils/dbUtils setup so the cleanup behavior is easy to find and reuse.tests/pw/tests/e2e/new-menu-manager/newMenuManager.spec.ts (1)
28-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnused
apiUtilsrequest context.
apiUtilsis created and disposed but never used to make a REST call anywhere in this file — all seeding goes throughNewMenuManagerPage's staticdbUtils-backed methods. Creating arequest.newContext()per file that's never exercised is unnecessary overhead copied from sibling specs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-menu-manager/newMenuManager.spec.ts` around lines 28 - 41, The test setup in NewMenuManager spec creates an unused ApiUtils/request.newContext lifecycle, which adds unnecessary overhead. Remove the apiUtils field and its initialization/disposal in test.beforeAll/test.afterAll, and keep the setup focused on NewMenuManagerPage methods such as snapshotOption and restoreOption, since all test seeding already goes through those static helpers.tests/pw/tests/e2e/new-product-addons/newProductAddonsPage.ts (2)
42-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded row-actions selector bypasses the shared
rowActionsBtnconstant.
newProductAddonsSelectors.rowActionsBtn(sourced from the sharedROW_ACTIONS_BTN) is defined butdeleteAddon()uses a hardcoded"button[aria-label='Actions']"string instead. If the shared constant is ever updated, this page object will silently diverge.🔧 Fix
- await row.locator("button[aria-label='Actions']").first().click(); + await row.locator(newProductAddonsSelectors.rowActionsBtn).first().click();Also applies to: 156-159
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-product-addons/newProductAddonsPage.ts` around lines 42 - 43, The `newProductAddonsPage` page object is bypassing the shared `newProductAddonsSelectors.rowActionsBtn` constant by using a hardcoded Actions selector in `deleteAddon()`. Update `deleteAddon` to reference the existing `rowActionsBtn` selector (the one backed by `ROW_ACTIONS_BTN`) so the page object stays consistent if the shared selector changes, and check the other mentioned usage in the same page object for the same pattern.
24-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStale/contradictory selector documentation.
Lines 27-30 state the Delete action fires "DIRECTLY with NO confirm modal (do NOT wait for a role=alertdialog)", but the very next paragraph (lines 31-33) says the opposite ("DOES open a plugin-ui alertdialog confirm... confirm it") — and the implementation in
deleteAddon()(lines 160-168) does wait for and confirm a dialog, matching the NOTE. Remove the outdated first claim to avoid confusing future maintainers who edit this selector block.📝 Suggested cleanup
-// A DataViews list (NO status tabs). Columns: Name (link to legacy ?edit=), -// Priority, Product Categories, Number of Fields. Search is CLIENT-SIDE (filters -// loaded rows by name) — use the fixed-debounce fallback, not a REST gate. The -// Delete row action fires DELETE /dokan/v1/vendor/product-addons/{id} DIRECTLY -// with NO confirm modal (do NOT wait for a role=alertdialog). The create / edit / -// import / export forms are LEGACY (settingsUrl?add=1 / ?edit=) → scoped out; the -// "Create New Addon" header link is asserted as an attribute only. -// NOTE (verified live): the Delete row action DOES open a plugin-ui alertdialog -// confirm (buttons Cancel / Delete) before the DELETE fires — confirm it. +// A DataViews list (NO status tabs). Columns: Name (link to legacy ?edit=), +// Priority, Product Categories, Number of Fields. Search is CLIENT-SIDE (filters +// loaded rows by name) — use the fixed-debounce fallback, not a REST gate. The +// create / edit / import / export forms are LEGACY (settingsUrl?add=1 / ?edit=) +// → scoped out; the "Create New Addon" header link is asserted as an attribute +// only. The Delete row action opens a plugin-ui alertdialog confirm (buttons +// Cancel / Delete) before firing DELETE /dokan/v1/vendor/product-addons/{id}.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-product-addons/newProductAddonsPage.ts` around lines 24 - 33, The selector documentation in newProductAddonsPage is contradictory about delete behavior: the earlier note says the Delete row action happens directly with no confirm modal, but the later NOTE and deleteAddon() implementation indicate it does open and must confirm a plugin-ui alertdialog. Update the comments in this block to remove the outdated “NO confirm modal” claim and keep the description aligned with deleteAddon() and the confirmed dialog flow.tests/pw/tests/e2e/new-product-advertising/newProductAdvertisingPage.ts (1)
3-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant duplicate import + dead
??fallback.
PHP_FATALis imported twice under two names (PHP_FATALandPHP_FATAL as FATAL), thenphpFatal: FATAL ?? PHP_FATALis effectively alwaysFATALsince both aliases resolve to the same non-nullish value. Simplify to a single import/reference.🧹 Cleanup
-import { REACT_ROOT, SEARCH_INPUT, PHP_FATAL, DATA_ROW_ANY, DATA_ROW_SETTLED, SKELETON_ANY, PHP_FATAL as FATAL, waitForRootReady as dvWaitForRootReady, hasNoPhpFatal as dvHasNoPhpFatal, fillDataViewsSearch } from '`@utils/dataViews`'; +import { REACT_ROOT, SEARCH_INPUT, PHP_FATAL, DATA_ROW_ANY, DATA_ROW_SETTLED, SKELETON_ANY, waitForRootReady as dvWaitForRootReady, hasNoPhpFatal as dvHasNoPhpFatal, fillDataViewsSearch } from '`@utils/dataViews`';- phpFatal: FATAL ?? PHP_FATAL, + phpFatal: PHP_FATAL,Also applies to: 31-31
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-product-advertising/newProductAdvertisingPage.ts` at line 3, The import list in newProductAdvertisingPage duplicates PHP_FATAL by importing it both directly and as FATAL, and the `phpFatal: FATAL ?? PHP_FATAL` fallback in the same module is dead code because both names point to the same value. Simplify the imports and the `phpFatal` assignment in `newProductAdvertisingPage` to use a single PHP_FATAL reference consistently, and remove the redundant alias so the locator setup remains clear.tests/pw/tests/e2e/new-followers/newFollowersPage.ts (2)
72-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
waitForSettlepolling loop across page objects.This exact skeleton/settled-row/empty-state polling logic is copy-pasted in
newProductAddonsPage.ts(lines 93-110) and a drifted variant innewProductAdvertisingPage.ts(lines 80-89, missing the empty-state branch). SincedataViews.tsalready centralizes DataViews readiness primitives, consider extracting a sharedwaitForListSettle(page, opts)helper there to prevent further divergence as more page objects are added in this wave.export async function waitForRootReady(page: Page, timeoutMs = 30000): Promise { await page.locator(REACT_ROOT).first().waitFor({ state: 'visible', timeout: timeoutMs }); }
♻️ Suggested direction
// tests/pw/utils/dataViews.ts export async function waitForListSettle( page: Page, opts: { skeleton?: string; settledRow?: string; emptyState?: string; columnHeader?: string; timeoutMs?: number } = {}, ): Promise<void> { const timeoutMs = opts.timeoutMs ?? 15000; const start = Date.now(); while (Date.now() - start < timeoutMs) { if ((await page.locator(opts.skeleton ?? SKELETON_ANY).count()) === 0) { if ((await page.locator(opts.settledRow ?? DATA_ROW_SETTLED).count()) > 0) return; if (opts.emptyState && (await page.getByText(opts.emptyState).first().isVisible().catch(() => false))) return; if (opts.columnHeader && (await page.locator(opts.columnHeader).count()) > 0) return; } await page.waitForTimeout(250); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-followers/newFollowersPage.ts` around lines 72 - 89, The waitForSettle polling logic in newFollowersPage is duplicated and already has drift in other page objects, so extract this skeleton/settled-row/empty-state/column-header readiness check into a shared helper in dataViews.ts (for example waitForListSettle). Update waitForSettle to delegate to that helper with the appropriate selectors, and reuse the same helper from newProductAddonsPage and newProductAdvertisingPage to keep the behavior consistent.
121-125: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueSearch REST-gate uses raw (unencoded) query in URL match.
decodeURIComponent(r.url()).includes(\search=${query}`)compares the decoded URL against the raw query string directly. For queries containing characters that need encoding (spaces,&,+), this comparison could silently fail to match, and the gate would just fall through on the.catch(() => undefined)` — masking a real assertion gap with a false "search settled" state. Low risk here since test queries are simple names, but worth being aware of for future callers passing less predictable strings.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-followers/newFollowersPage.ts` around lines 121 - 125, The search response gate in newFollowersPage.waitForSearch is matching the decoded URL against the raw query string, so queries with encoded characters may never be detected. Update the waitForResponse predicate to compare against the properly encoded search value (or decode both sides consistently) while keeping the newFollowersSelectors.listRest and GET checks intact. Also avoid silently swallowing a missed match via the catch fallback if the gate is meant to verify the search request actually happened.tests/pw/tests/e2e/new-auction/newAuction.spec.ts (1)
212-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared
REACT_ROOTconstant here.
This selector is already exported from@utils/dataViewsand used elsewhere; hardcoding'#dokan-vendor-dashboard-root'here adds drift risk for no behavioral gain.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-auction/newAuction.spec.ts` around lines 212 - 220, The React mount assertion in the `test('a logged-in customer cannot mount the vendor Auctions route (React)')` test is hardcoding the dashboard root selector instead of using the shared `REACT_ROOT` constant. Update this check to reference `REACT_ROOT` from `@utils/dataViews` in `newAuction.spec.ts` so the test stays aligned with the other UI tests and avoids selector drift.tests/pw/tests/e2e/new-product-form/newProductFormEdit.spec.ts (1)
21-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSeeded products are never cleaned up.
Every test calls
seedProduct()to create a real product via REST, but unlike the siblingnewProductFormTypes.spec.tsandnewProductFormWholesale.spec.ts(which push ids intoseededIdsand delete them inafterAll), this file has no tracking/cleanup array. With 10 tests in this file, each run leaves behind orphaned products in the test environment, accumulating over repeated CI runs.♻️ Suggested cleanup pattern
let apiUtils: ApiUtils; const stamp = (): string => `${Date.now()}${Math.floor(Math.random() * 1000)}`; +const seededIds: string[] = []; async function seedProduct(overrides: Record<string, unknown> = {}): Promise<string> { const [, id] = await apiUtils.createProduct({ ...payloads.createProductRequiredFields(), regular_price: '50', ...overrides }, payloads.vendorAuth); + seededIds.push(id); return id; } test.beforeAll(async () => { apiUtils = new ApiUtils(await request.newContext()); }); test.afterAll(async () => { + for (const id of seededIds) await apiUtils.deleteProduct(id, payloads.vendorAuth, true).catch(() => undefined); await apiUtils?.dispose(); });Also applies to: 120-172
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/pw/tests/e2e/new-product-form/newProductFormEdit.spec.ts` around lines 21 - 33, The seedProduct helper currently creates real products but there is no cleanup path, so products accumulate across runs. Add a tracking array similar to the sibling newProductFormTypes.spec.ts and newProductFormWholesale.spec.ts files, push each created id from seedProduct() into it, and delete those ids in an afterAll cleanup. Keep the fix centered around seedProduct and the test suite’s teardown so all tests that call it are covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/pw/tests/e2e/new-orders/newOrdersGaps.spec.ts`:
- Around line 26-44: The Vendor orders behavioral-gaps suite seeds orders via
seedOrder and stores them in seededOrderIds, but the first describe block’s
afterAll only disposes apiUtils and never removes those orders. Update the
afterAll in test.describe('Vendor orders list — behavioral gaps (React)') to
iterate over seededOrderIds and cancel/delete each seeded order before disposing
the API context, matching the cleanup approach used in the later describe block
that clears gateOrderIds.
In `@tests/pw/tests/e2e/new-product-form/newProductFormPage.ts`:
- Around line 695-700: The fallback in saveIsDisabled() is inverted: it
currently treats an error from saveButton/isEnabled() as enabled, which makes
the helper report “not disabled” when the button can’t be queried. Update
saveIsDisabled() in NewProductFormPage to match the safer pattern used elsewhere
in the class (for example save()), so any wait/query failure defaults to
false/disabled rather than true/enabled; this keeps the saveButton guard in
newProductFormEdit.spec.ts from clicking when the button state is uncertain.
- Around line 684-693: The saveEdit() flow currently waits for the click even
when the saveButton remains disabled, unlike save(), which exits early in that
case. Update saveEdit() in NewProductFormPage to use the same disabled-button
guard before the Promise.all that includes btn.click(), so validation-blocked
edits return immediately instead of timing out.
In `@tests/pw/tests/e2e/new-product-form/newProductFormWholesale.spec.ts`:
- Around line 65-79: `NewProductFormPage.enableWholesale()` still uses stale
wholesale price and quantity selectors, so the shared helper only toggles the
checkbox instead of filling the fields. Update the helper in
`tests/pw/tests/e2e/new-product-form/newProductFormPage.ts` to use the same
label-based lookup pattern used in this spec, targeting the wholesale inputs by
their labels rather than old ids/names. Then change the new-product form tests
to call `form.enableWholesale('60', '10')` so the behavior stays centralized and
both fields are populated through the shared page object method.
In `@tests/pw/tests/e2e/new-requested-quotes/newRequestedQuotesPage.ts`:
- Around line 181-211: The rowActionItems helper contains a dead, unused check
callback and duplicates the same visibility logic inline for view, trash, and
restore. Remove the unused helper or replace the repeated checks by actually
calling a single shared helper, and make sure any generic lookup in
rowActionItems uses the correct selector strategy for string vs RegExp labels
instead of falling back to ROW_ACTIONS_BTN. Keep the behavior in
newRequestedQuotesPage.rowActionItems consistent and eliminate the void check
workaround.
In `@tests/pw/tests/e2e/vendor-delivery-time/vendorDeliveryTime.spec.ts`:
- Line 105: The storefront delivery-time React suite is incorrectly skipped,
which removes active coverage for the product-page tests in
vendorDeliveryTime.spec.ts. Unskip the test block by removing the describe.skip
wrapper around the Delivery Time Front-end (React) Tests `@pro` suite so the
storefront cases under the vendorDeliveryTime spec run again, and keep the
existing product-page coverage intact.
---
Nitpick comments:
In `@tests/pw/feature-map/feature-map.yml`:
- Around line 785-801: Move the mixed vendor/customer coupon-usage scenario out
of the vendor (new UI) group and into the appropriate cross-role category used
elsewhere in feature-map.yml, such as business flow (new UI) or cross-role (new
UI). Keep the scenario text unchanged, but relocate the unique coupon usage
entry so the grouping matches the established convention alongside the related
vendor and customer sections.
In `@tests/pw/tests/e2e/announcements/announcements.spec.ts`:
- Around line 483-539: The added `@new-ui` tags in the New Vendor Announcement
test cases are currently ineffective because the entire block is wrapped in
test.describe.skip, so they won’t affect selection unless the suite is
unskipped. If the intent is to keep metadata only, no code change is needed;
otherwise, move the tag updates into an active describe/test structure in
announcements.spec.ts using the existing test.describe.skip and individual test
declarations as the reference points.
In `@tests/pw/tests/e2e/new-auction/newAuction.spec.ts`:
- Around line 212-220: The React mount assertion in the `test('a logged-in
customer cannot mount the vendor Auctions route (React)')` test is hardcoding
the dashboard root selector instead of using the shared `REACT_ROOT` constant.
Update this check to reference `REACT_ROOT` from `@utils/dataViews` in
`newAuction.spec.ts` so the test stays aligned with the other UI tests and
avoids selector drift.
In `@tests/pw/tests/e2e/new-followers/newFollowersPage.ts`:
- Around line 72-89: The waitForSettle polling logic in newFollowersPage is
duplicated and already has drift in other page objects, so extract this
skeleton/settled-row/empty-state/column-header readiness check into a shared
helper in dataViews.ts (for example waitForListSettle). Update waitForSettle to
delegate to that helper with the appropriate selectors, and reuse the same
helper from newProductAddonsPage and newProductAdvertisingPage to keep the
behavior consistent.
- Around line 121-125: The search response gate in
newFollowersPage.waitForSearch is matching the decoded URL against the raw query
string, so queries with encoded characters may never be detected. Update the
waitForResponse predicate to compare against the properly encoded search value
(or decode both sides consistently) while keeping the
newFollowersSelectors.listRest and GET checks intact. Also avoid silently
swallowing a missed match via the catch fallback if the gate is meant to verify
the search request actually happened.
In `@tests/pw/tests/e2e/new-menu-manager/newMenuManager.spec.ts`:
- Around line 28-41: The test setup in NewMenuManager spec creates an unused
ApiUtils/request.newContext lifecycle, which adds unnecessary overhead. Remove
the apiUtils field and its initialization/disposal in
test.beforeAll/test.afterAll, and keep the setup focused on NewMenuManagerPage
methods such as snapshotOption and restoreOption, since all test seeding already
goes through those static helpers.
In `@tests/pw/tests/e2e/new-product-addons/newProductAddonsPage.ts`:
- Around line 42-43: The `newProductAddonsPage` page object is bypassing the
shared `newProductAddonsSelectors.rowActionsBtn` constant by using a hardcoded
Actions selector in `deleteAddon()`. Update `deleteAddon` to reference the
existing `rowActionsBtn` selector (the one backed by `ROW_ACTIONS_BTN`) so the
page object stays consistent if the shared selector changes, and check the other
mentioned usage in the same page object for the same pattern.
- Around line 24-33: The selector documentation in newProductAddonsPage is
contradictory about delete behavior: the earlier note says the Delete row action
happens directly with no confirm modal, but the later NOTE and deleteAddon()
implementation indicate it does open and must confirm a plugin-ui alertdialog.
Update the comments in this block to remove the outdated “NO confirm modal”
claim and keep the description aligned with deleteAddon() and the confirmed
dialog flow.
In `@tests/pw/tests/e2e/new-product-advertising/newProductAdvertisingPage.ts`:
- Line 3: The import list in newProductAdvertisingPage duplicates PHP_FATAL by
importing it both directly and as FATAL, and the `phpFatal: FATAL ?? PHP_FATAL`
fallback in the same module is dead code because both names point to the same
value. Simplify the imports and the `phpFatal` assignment in
`newProductAdvertisingPage` to use a single PHP_FATAL reference consistently,
and remove the redundant alias so the locator setup remains clear.
In `@tests/pw/tests/e2e/new-product-form/newProductFormEdit.spec.ts`:
- Around line 21-33: The seedProduct helper currently creates real products but
there is no cleanup path, so products accumulate across runs. Add a tracking
array similar to the sibling newProductFormTypes.spec.ts and
newProductFormWholesale.spec.ts files, push each created id from seedProduct()
into it, and delete those ids in an afterAll cleanup. Keep the fix centered
around seedProduct and the test suite’s teardown so all tests that call it are
covered.
In `@tests/pw/tests/e2e/new-seller-badge/newSellerBadge.spec.ts`:
- Around line 188-199: The cleanup logic in newSellerBadge.spec.ts repeats the
same acquired-rows DELETE and currently hides every DB failure with catch(() =>
undefined), which can mask bad DB_PREFIX/VENDOR_ID values or real query errors.
Extract the repeated DELETE FROM ${DB_PREFIX}_dokan_seller_badge_acquired WHERE
vendor_id = ? into a small helper used by the affected test hooks, and change
the cleanup path in test.afterAll (and the other matching cleanup sites) to
report failures with context instead of silently swallowing them. Keep the
helper close to the existing apiUtils/dbUtils setup so the cleanup behavior is
easy to find and reuse.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b13f2bd6-7ac9-493d-9139-d6e9700d234d
📒 Files selected for processing (52)
tests/pw/CONVERSION-LOG.mdtests/pw/feature-map/feature-map.ymltests/pw/tests/e2e/announcements/announcements.spec.tstests/pw/tests/e2e/follow-store/followStore.spec.tstests/pw/tests/e2e/new-announcements/newAnnouncements.spec.tstests/pw/tests/e2e/new-announcements/newAnnouncementsPage.tstests/pw/tests/e2e/new-auction/newAuction.spec.tstests/pw/tests/e2e/new-auction/newAuctionPage.tstests/pw/tests/e2e/new-booking/newBooking.spec.tstests/pw/tests/e2e/new-booking/newBookingPage.tstests/pw/tests/e2e/new-delivery-time/newDeliveryTime.spec.tstests/pw/tests/e2e/new-followers/bugs/follow-store-followers-cache-stale.mdtests/pw/tests/e2e/new-followers/newFollowers.spec.tstests/pw/tests/e2e/new-followers/newFollowersPage.tstests/pw/tests/e2e/new-menu-manager/newMenuManager.spec.tstests/pw/tests/e2e/new-menu-manager/newMenuManagerPage.tstests/pw/tests/e2e/new-orders/newOrdersGaps.spec.tstests/pw/tests/e2e/new-product-addons/newProductAddons.spec.tstests/pw/tests/e2e/new-product-addons/newProductAddonsPage.tstests/pw/tests/e2e/new-product-advertising/newProductAdvertising.spec.tstests/pw/tests/e2e/new-product-advertising/newProductAdvertisingPage.tstests/pw/tests/e2e/new-product-form/newProductForm.spec.tstests/pw/tests/e2e/new-product-form/newProductFormAdvanced.spec.tstests/pw/tests/e2e/new-product-form/newProductFormEdit.spec.tstests/pw/tests/e2e/new-product-form/newProductFormPage.tstests/pw/tests/e2e/new-product-form/newProductFormTypes.spec.tstests/pw/tests/e2e/new-product-form/newProductFormValidation.spec.tstests/pw/tests/e2e/new-product-form/newProductFormWholesale.spec.tstests/pw/tests/e2e/new-products/newProductsProActions.spec.tstests/pw/tests/e2e/new-requested-quotes/newRequestedQuotes.spec.tstests/pw/tests/e2e/new-requested-quotes/newRequestedQuotesPage.tstests/pw/tests/e2e/new-seller-badge/newSellerBadge.spec.tstests/pw/tests/e2e/new-seller-badge/newSellerBadgePage.tstests/pw/tests/e2e/new-shipstation/newShipstation.spec.tstests/pw/tests/e2e/new-shipstation/newShipstationPage.tstests/pw/tests/e2e/new-subscription/newSubscription.spec.tstests/pw/tests/e2e/new-subscription/newSubscriptionPage.tstests/pw/tests/e2e/new-user-subscription/newUserSubscription.spec.tstests/pw/tests/e2e/new-user-subscription/newUserSubscriptionPage.tstests/pw/tests/e2e/new-withdraw/newWithdrawB15.spec.tstests/pw/tests/e2e/product-addons/productAddons.spec.tstests/pw/tests/e2e/product-advertising/productAdvertising.spec.tstests/pw/tests/e2e/products-details/productsDetails.spec.tstests/pw/tests/e2e/request-for-quotes/requestForQuotes.spec.tstests/pw/tests/e2e/seller-badges/sellerBadges.spec.tstests/pw/tests/e2e/shipstation/shipstation.spec.tstests/pw/tests/e2e/vendor-auction/vendorAuction.spec.tstests/pw/tests/e2e/vendor-booking/vendorBooking.spec.tstests/pw/tests/e2e/vendor-delivery-time/vendorDeliveryTime.spec.tstests/pw/tests/e2e/vendor-product-subscription/vendorProductSubscription.spec.tstests/pw/tests/e2e/vendor-subscriptions/vendorSubscriptions.spec.tstests/pw/utils/shard-durations.json
💤 Files with no reviewable changes (3)
- tests/pw/tests/e2e/new-product-form/newProductFormAdvanced.spec.ts
- tests/pw/tests/e2e/new-product-form/newProductFormValidation.spec.ts
- tests/pw/tests/e2e/new-product-form/newProductForm.spec.ts
✅ Files skipped from review due to trivial changes (2)
- tests/pw/tests/e2e/product-advertising/productAdvertising.spec.ts
- tests/pw/utils/shard-durations.json
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/pw/tests/e2e/vendor-subscriptions/vendorSubscriptions.spec.ts
- tests/pw/tests/e2e/new-seller-badge/newSellerBadgePage.ts
| const seededOrderIds: string[] = []; | ||
|
|
||
| const round2 = (n: number): number => Math.round(n * 100) / 100; | ||
|
|
||
| /** Seed one order in the given status owned by the fixture vendor; returns its id. */ | ||
| async function seedOrder(status = 'wc-processing'): Promise<string> { | ||
| const [, , orderId] = await apiUtils.createOrderWithStatus(PRODUCT_ID as string, { ...payloads.createOrder }, status, payloads.vendorAuth); | ||
| seededOrderIds.push(orderId); | ||
| return orderId; | ||
| } | ||
|
|
||
| test.describe('Vendor orders list — behavioral gaps (React)', () => { | ||
| test.beforeAll(async () => { | ||
| apiUtils = new ApiUtils(await request.newContext()); | ||
| }); | ||
|
|
||
| test.afterAll(async () => { | ||
| await apiUtils?.dispose(); | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Seeded orders in this describe block are never cleaned up.
seededOrderIds is populated by seedOrder() (Line 33) but this describe block's afterAll (Lines 42-44) only disposes apiUtils — it never cancels/deletes the orders it seeded, unlike the second describe block below (Line 126) which explicitly cancels its gateOrderIds. This leaves test orders behind on the shared/live DB across runs.
🧹 Proposed fix
test.afterAll(async () => {
+ for (const id of seededOrderIds) await apiUtils.updateOrderStatus(id, 'cancelled', payloads.adminAuth).catch(() => undefined);
await apiUtils?.dispose();
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const seededOrderIds: string[] = []; | |
| const round2 = (n: number): number => Math.round(n * 100) / 100; | |
| /** Seed one order in the given status owned by the fixture vendor; returns its id. */ | |
| async function seedOrder(status = 'wc-processing'): Promise<string> { | |
| const [, , orderId] = await apiUtils.createOrderWithStatus(PRODUCT_ID as string, { ...payloads.createOrder }, status, payloads.vendorAuth); | |
| seededOrderIds.push(orderId); | |
| return orderId; | |
| } | |
| test.describe('Vendor orders list — behavioral gaps (React)', () => { | |
| test.beforeAll(async () => { | |
| apiUtils = new ApiUtils(await request.newContext()); | |
| }); | |
| test.afterAll(async () => { | |
| await apiUtils?.dispose(); | |
| }); | |
| const seededOrderIds: string[] = []; | |
| const round2 = (n: number): number => Math.round(n * 100) / 100; | |
| /** Seed one order in the given status owned by the fixture vendor; returns its id. */ | |
| async function seedOrder(status = 'wc-processing'): Promise<string> { | |
| const [, , orderId] = await apiUtils.createOrderWithStatus(PRODUCT_ID as string, { ...payloads.createOrder }, status, payloads.vendorAuth); | |
| seededOrderIds.push(orderId); | |
| return orderId; | |
| } | |
| test.describe('Vendor orders list — behavioral gaps (React)', () => { | |
| test.beforeAll(async () => { | |
| apiUtils = new ApiUtils(await request.newContext()); | |
| }); | |
| test.afterAll(async () => { | |
| for (const id of seededOrderIds) await apiUtils.updateOrderStatus(id, 'cancelled', payloads.adminAuth).catch(() => undefined); | |
| await apiUtils?.dispose(); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/new-orders/newOrdersGaps.spec.ts` around lines 26 - 44,
The Vendor orders behavioral-gaps suite seeds orders via seedOrder and stores
them in seededOrderIds, but the first describe block’s afterAll only disposes
apiUtils and never removes those orders. Update the afterAll in
test.describe('Vendor orders list — behavioral gaps (React)') to iterate over
seededOrderIds and cancel/delete each seeded order before disposing the API
context, matching the cleanup approach used in the later describe block that
clears gateOrderIds.
| async saveEdit(): Promise<void> { | ||
| const btn = this.saveButton; | ||
| await btn.waitFor({ state: 'visible' }); | ||
| await expect(btn) | ||
| .toBeEnabled({ timeout: 8000 }) | ||
| .catch(() => undefined); | ||
| await Promise.all([this.page.waitForResponse(r => /dokan\/v[0-9]+\/products\/\d+/i.test(r.url()) && r.request().method() === 'PUT', { timeout: 20000 }).catch(() => undefined), btn.click()]); | ||
| await this.successNotice.waitFor({ state: 'visible', timeout: 10000 }).catch(() => undefined); | ||
| await this.page.waitForTimeout(500); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file='tests/pw/tests/e2e/new-product-form/newProductFormPage.ts'
wc -l "$file"
sed -n '580,720p' "$file"
printf '\n--- saveEdit usages ---\n'
rg -n "saveEdit\(" tests/pw -S
printf '\n--- save usages ---\n'
rg -n "save\(" tests/pw -SRepository: getdokan/dokan
Length of output: 10362
Add the disabled-button guard to saveEdit()
save() returns early when the button stays disabled; saveEdit() should do the same before btn.click() so validation-blocked edits don’t wait on the click timeout.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/new-product-form/newProductFormPage.ts` around lines 684 -
693, The saveEdit() flow currently waits for the click even when the saveButton
remains disabled, unlike save(), which exits early in that case. Update
saveEdit() in NewProductFormPage to use the same disabled-button guard before
the Promise.all that includes btn.click(), so validation-blocked edits return
immediately instead of timing out.
| /** Is the Update Product button currently disabled (the "blocked save" signal)? */ | ||
| async saveIsDisabled(): Promise<boolean> { | ||
| const btn = this.saveButton; | ||
| await btn.waitFor({ state: 'visible', timeout: 8000 }).catch(() => undefined); | ||
| return !(await btn.isEnabled().catch(() => true)); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Inconsistent error fallback in saveIsDisabled().
return !(await btn.isEnabled().catch(() => true)); defaults isEnabled() to true on error, so if the button can't be located/queried, this function reports "not disabled" (safe to click). Every other guard in this file (e.g. save() line 615) defaults to false on error — i.e. assume NOT enabled/safe-to-act when uncertain. This inversion means newProductFormEdit.spec.ts's "cannot set a sale price above regular price" test (if (!(await form.saveIsDisabled())) await form.saveButton.click();) could attempt to click a button it failed to even query.
🔧 Suggested fix
async saveIsDisabled(): Promise<boolean> {
const btn = this.saveButton;
await btn.waitFor({ state: 'visible', timeout: 8000 }).catch(() => undefined);
- return !(await btn.isEnabled().catch(() => true));
+ return !(await btn.isEnabled().catch(() => false));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** Is the Update Product button currently disabled (the "blocked save" signal)? */ | |
| async saveIsDisabled(): Promise<boolean> { | |
| const btn = this.saveButton; | |
| await btn.waitFor({ state: 'visible', timeout: 8000 }).catch(() => undefined); | |
| return !(await btn.isEnabled().catch(() => true)); | |
| } | |
| /** Is the Update Product button currently disabled (the "blocked save" signal)? */ | |
| async saveIsDisabled(): Promise<boolean> { | |
| const btn = this.saveButton; | |
| await btn.waitFor({ state: 'visible', timeout: 8000 }).catch(() => undefined); | |
| return !(await btn.isEnabled().catch(() => false)); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/new-product-form/newProductFormPage.ts` around lines 695 -
700, The fallback in saveIsDisabled() is inverted: it currently treats an error
from saveButton/isEnabled() as enabled, which makes the helper report “not
disabled” when the button can’t be queried. Update saveIsDisabled() in
NewProductFormPage to match the safer pattern used elsewhere in the class (for
example save()), so any wait/query failure defaults to false/disabled rather
than true/enabled; this keeps the saveButton guard in newProductFormEdit.spec.ts
from clicking when the button state is uncertain.
| // NOTE (verified live): the shared enableWholesale() targets input[name="wholesale_price"] | ||
| // / #dokan-form-field-wholesale_price which no longer exist — the base-ui fields render | ||
| // with auto-generated ids, so target them by their field LABEL instead. | ||
| await form.setCheckbox('Enable wholesale for this product', true); | ||
| const fieldByLabel = (label: RegExp) => | ||
| page | ||
| .locator('.components-base-control') | ||
| .filter({ has: page.locator('.dokan-form-field-label', { hasText: label }) }) | ||
| .locator('input') | ||
| .first(); | ||
| const priceInput = fieldByLabel(/^Wholesale Price/i); | ||
| const qtyInput = fieldByLabel(/Minimum Quantity for Wholesale/i); | ||
| await priceInput.waitFor({ state: 'visible', timeout: 10000 }); | ||
| await priceInput.fill('60'); | ||
| await qtyInput.fill('10'); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether enableWholesale() is still called anywhere (would be silently broken).
rg -nP '\.enableWholesale\s*\(' --type=tsRepository: getdokan/dokan
Length of output: 152
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Search definitions/usages ==\n'
rg -n --hidden --glob '!**/node_modules/**' 'enableWholesale' tests
printf '\n== Page object file list ==\n'
git ls-files 'tests/pw/tests/e2e/new-product-form/*'
printf '\n== Inspect page object ==\n'
sed -n '1,240p' tests/pw/tests/e2e/new-product-form/newProductFormPage.ts
printf '\n== Inspect spec around both noted sections ==\n'
sed -n '1,180p' tests/pw/tests/e2e/new-product-form/newProductFormWholesale.spec.tsRepository: getdokan/dokan
Length of output: 19212
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== enableWholesale implementation ==\n'
sed -n '800,860p' tests/pw/tests/e2e/new-product-form/newProductFormPage.ts
printf '\n== enableWholesale usage in advanced spec ==\n'
sed -n '90,140p' tests/pw/tests/e2e/new-product-form/newProductFormAdvanced.spec.tsRepository: getdokan/dokan
Length of output: 4939
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== selector occurrences ==\n'
rg -n --hidden --glob '!**/node_modules/**' 'dokan-form-field-wholesale_(price|quantity)|input\[name="wholesale_(price|quantity)"\]|Wholesale Price|Minimum Quantity for Wholesale' tests/pw
printf '\n== conversion log excerpt around note ==\n'
sed -n '530,560p' tests/pw/CONVERSION-LOG.md
printf '\n== nearby helper/label lookup in spec ==\n'
sed -n '55,135p' tests/pw/tests/e2e/new-product-form/newProductFormWholesale.spec.tsRepository: getdokan/dokan
Length of output: 8851
Update NewProductFormPage.enableWholesale() to use the label-based fields
form.enableWholesale('60', '10') still points at stale wholesale price/quantity selectors, so it only toggles the checkbox and skips filling the inputs. Reuse the same label-based lookup in tests/pw/tests/e2e/new-product-form/newProductFormPage.ts and have both tests call the shared helper instead of duplicating it here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/new-product-form/newProductFormWholesale.spec.ts` around
lines 65 - 79, `NewProductFormPage.enableWholesale()` still uses stale wholesale
price and quantity selectors, so the shared helper only toggles the checkbox
instead of filling the fields. Update the helper in
`tests/pw/tests/e2e/new-product-form/newProductFormPage.ts` to use the same
label-based lookup pattern used in this spec, targeting the wholesale inputs by
their labels rather than old ids/names. Then change the new-product form tests
to call `form.enableWholesale('60', '10')` so the behavior stays centralized and
both fields are populated through the shared page object method.
| async rowActionItems(text: string): Promise<{ view: boolean; trash: boolean; restore: boolean }> { | ||
| const row = this.rowsWithText(text).first(); | ||
| await row.waitFor({ state: 'visible', timeout: 15000 }); | ||
| await row.locator("button[aria-label='Actions']").first().click(); | ||
| const check = async (label: string | RegExp): Promise<boolean> => | ||
| this.page | ||
| .locator(typeof label === 'string' ? newRequestedQuotesSelectors.actionMenuItem(label) : ROW_ACTIONS_BTN) | ||
| .first() | ||
| .isVisible({ timeout: 2000 }) | ||
| .catch(() => false); | ||
| const result = { | ||
| view: await this.page | ||
| .locator(newRequestedQuotesSelectors.actionMenuItem('View')) | ||
| .first() | ||
| .isVisible({ timeout: 2000 }) | ||
| .catch(() => false), | ||
| trash: await this.page | ||
| .getByRole('menuitem', { name: /Move to Trash/i }) | ||
| .first() | ||
| .isVisible({ timeout: 2000 }) | ||
| .catch(() => false), | ||
| restore: await this.page | ||
| .getByRole('menuitem', { name: /Pending|Restore/i }) | ||
| .first() | ||
| .isVisible({ timeout: 2000 }) | ||
| .catch(() => false), | ||
| }; | ||
| void check; | ||
| await this.page.keyboard.press('Escape').catch(() => undefined); | ||
| return result; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Dead, unused check helper duplicated inline instead of being called.
check (Lines 185-190) is defined but never invoked — void check; (Line 208) exists purely to silence the unused-variable warning, while view/trash/restore (Lines 192-207) re-implement the exact same isVisible().catch(() => false) pattern by hand. The helper is also buggy as-is: for a RegExp label it falls back to the unrelated ROW_ACTIONS_BTN selector instead of using the regex for a role-based lookup, so it isn't even correct if wired up.
♻️ Proposed cleanup
- const check = async (label: string | RegExp): Promise<boolean> =>
- this.page
- .locator(typeof label === 'string' ? newRequestedQuotesSelectors.actionMenuItem(label) : ROW_ACTIONS_BTN)
- .first()
- .isVisible({ timeout: 2000 })
- .catch(() => false);
+ const checkMenuItem = async (name: string | RegExp): Promise<boolean> =>
+ this.page.getByRole('menuitem', { name }).first().isVisible({ timeout: 2000 }).catch(() => false);
const result = {
- view: await this.page
- .locator(newRequestedQuotesSelectors.actionMenuItem('View'))
- .first()
- .isVisible({ timeout: 2000 })
- .catch(() => false),
- trash: await this.page
- .getByRole('menuitem', { name: /Move to Trash/i })
- .first()
- .isVisible({ timeout: 2000 })
- .catch(() => false),
- restore: await this.page
- .getByRole('menuitem', { name: /Pending|Restore/i })
- .first()
- .isVisible({ timeout: 2000 })
- .catch(() => false),
+ view: await checkMenuItem(/View/i),
+ trash: await checkMenuItem(/Move to Trash/i),
+ restore: await checkMenuItem(/Pending|Restore/i),
};
- void check;
await this.page.keyboard.press('Escape').catch(() => undefined);
return result;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async rowActionItems(text: string): Promise<{ view: boolean; trash: boolean; restore: boolean }> { | |
| const row = this.rowsWithText(text).first(); | |
| await row.waitFor({ state: 'visible', timeout: 15000 }); | |
| await row.locator("button[aria-label='Actions']").first().click(); | |
| const check = async (label: string | RegExp): Promise<boolean> => | |
| this.page | |
| .locator(typeof label === 'string' ? newRequestedQuotesSelectors.actionMenuItem(label) : ROW_ACTIONS_BTN) | |
| .first() | |
| .isVisible({ timeout: 2000 }) | |
| .catch(() => false); | |
| const result = { | |
| view: await this.page | |
| .locator(newRequestedQuotesSelectors.actionMenuItem('View')) | |
| .first() | |
| .isVisible({ timeout: 2000 }) | |
| .catch(() => false), | |
| trash: await this.page | |
| .getByRole('menuitem', { name: /Move to Trash/i }) | |
| .first() | |
| .isVisible({ timeout: 2000 }) | |
| .catch(() => false), | |
| restore: await this.page | |
| .getByRole('menuitem', { name: /Pending|Restore/i }) | |
| .first() | |
| .isVisible({ timeout: 2000 }) | |
| .catch(() => false), | |
| }; | |
| void check; | |
| await this.page.keyboard.press('Escape').catch(() => undefined); | |
| return result; | |
| } | |
| async rowActionItems(text: string): Promise<{ view: boolean; trash: boolean; restore: boolean }> { | |
| const row = this.rowsWithText(text).first(); | |
| await row.waitFor({ state: 'visible', timeout: 15000 }); | |
| await row.locator("button[aria-label='Actions']").first().click(); | |
| const checkMenuItem = async (name: string | RegExp): Promise<boolean> => | |
| this.page.getByRole('menuitem', { name }).first().isVisible({ timeout: 2000 }).catch(() => false); | |
| const result = { | |
| view: await checkMenuItem(/View/i), | |
| trash: await checkMenuItem(/Move to Trash/i), | |
| restore: await checkMenuItem(/Pending|Restore/i), | |
| }; | |
| await this.page.keyboard.press('Escape').catch(() => undefined); | |
| return result; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/new-requested-quotes/newRequestedQuotesPage.ts` around
lines 181 - 211, The rowActionItems helper contains a dead, unused check
callback and duplicates the same visibility logic inline for view, trash, and
restore. Remove the unused helper or replace the repeated checks by actually
calling a single shared helper, and make sure any generic lookup in
rowActionItems uses the correct selector strategy for string vs RegExp labels
instead of falling back to ROW_ACTIONS_BTN. Keep the behavior in
newRequestedQuotesPage.rowActionItems consistent and eliminate the void check
workaround.
| // the legacy tests above for parity coverage during rollout. | ||
|
|
||
| test.describe('Delivery Time Front-end (React) Tests @pro', () => { | ||
| test.describe.skip('Delivery Time Front-end (React) Tests @pro', () => { |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
sed -n '105,180p' tests/pw/tests/e2e/vendor-delivery-time/vendorDeliveryTime.spec.tsRepository: getdokan/dokan
Length of output: 1459
🏁 Script executed:
#!/bin/bash
cat -n tests/pw/tests/e2e/vendor-delivery-time/vendorDeliveryTime.spec.ts | sed -n '1,220p'Repository: getdokan/dokan
Length of output: 7898
Unskip the storefront delivery-time React suite
tests/pw/tests/e2e/vendor-delivery-time/vendorDeliveryTime.spec.ts:105 is product-page coverage (product/p1_v1-simple/ with @guest), not vendor-dashboard parity. Keeping this describe.skip drops storefront coverage that should stay active.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/pw/tests/e2e/vendor-delivery-time/vendorDeliveryTime.spec.ts` at line
105, The storefront delivery-time React suite is incorrectly skipped, which
removes active coverage for the product-page tests in
vendorDeliveryTime.spec.ts. Unskip the test block by removing the describe.skip
wrapper around the Delivery Time Front-end (React) Tests `@pro` suite so the
storefront cases under the vendorDeliveryTime spec run again, and keep the
existing product-page coverage intact.
Source: Coding guidelines
… the media Library is empty The "submit a verification request with an uploaded document" test picked an existing wp.media Library attachment. A vendor's media modal shows only the vendor's OWN media, so the admin-seeded attachment is invisible and a clean env (CI) has an empty Library → `.media-modal .attachment` timed out (deterministic, all 3 retries) → shard 6 red. Make uploadFromMediaLibrary robust: keep the fast path (use an existing attachment when present) but, when the Library is empty, upload a fixture through the "Upload Files" tab (uploaded as the current vendor session → always appears + auto-selects). Selectors mirror the proven newProductFormPage media upload (button#menu-item-upload + modal-scoped input[type=file]); a freshly-uploaded item is auto-selected, so we avoid re-clicking (which would deselect it). Fast path re-verified green locally.
…bug), unblock CI CI (PR #3303, shard 9) deterministically fails SE-PAY-09: under an admin-absorbed marketplace coupon on a multi-vendor cart, a vendor receives TWO Stripe transfers for the same charge (Expected 1, Received 2, polled 60s, all retries). The counter is strict (source_transaction === chargeId, to that vendor's account) and the product creates exactly one idempotent transfer per sub-order with no coupon-compensation path — so this is an unexplained double transfer (potential double-payment). SE-PAY-01/02 pass, so the admin coupon is the trigger. Do NOT relax the assertion to accept 2 (that would mask a possible double-payment). Mark the test test.fixme with a filed bug (bugs/se-pay-09-double-transfer-admin-coupon.md) so CI is unblocked honestly and the Stripe-Express owner can root-cause: real disbursement bug vs an intended compensation transfer (in which case the test should sum transfers per vendor, keeping the Σtransfers ≤ charge safety invariant). Unrelated to the new-UI conversion — surfaced by CI.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@tests/pw/tests/e2e/stripe-express/bugs/se-pay-09-double-transfer-admin-coupon.md`:
- Around line 13-17: The fenced code block in the markdown test note is missing
a language tag, triggering markdownlint MD040. Update the fenced block in the
comment content to use a language identifier such as text while keeping the
displayed output unchanged, so the doc remains lint-clean.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9dd28c10-dbdd-4ac7-9b66-38c9ffb908de
📒 Files selected for processing (2)
tests/pw/tests/e2e/stripe-express/bugs/se-pay-09-double-transfer-admin-coupon.mdtests/pw/tests/e2e/stripe-express/stripeExpressPayouts.spec.ts
| ``` | ||
| firstVendorTransfer() → expect.poll(transfersForChargeToVendor(chargeId, acct).length).toBe(1) | ||
| Expected: 1 | ||
| Received: 2 (Timeout 60000ms exceeded while waiting on the predicate) | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language tag to this fenced block.
markdownlint flags this as MD040; specifying a language (for example, text) keeps the doc lint-clean without changing the content.
♻️ Proposed fix
-```
+```text
firstVendorTransfer() → expect.poll(transfersForChargeToVendor(chargeId, acct).length).toBe(1)
Expected: 1
Received: 2 (Timeout 60000ms exceeded while waiting on the predicate)
-```
+```📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| firstVendorTransfer() → expect.poll(transfersForChargeToVendor(chargeId, acct).length).toBe(1) | |
| Expected: 1 | |
| Received: 2 (Timeout 60000ms exceeded while waiting on the predicate) | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 13-13: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@tests/pw/tests/e2e/stripe-express/bugs/se-pay-09-double-transfer-admin-coupon.md`
around lines 13 - 17, The fenced code block in the markdown test note is missing
a language tag, triggering markdownlint MD040. Update the fenced block in the
comment content to use a language identifier such as text while keeping the
displayed output unchanged, so the doc remains lint-clean.
Source: Linters/SAST tools
All Submissions:
Changes proposed in this Pull Request:
Related Pull Request(s)
Closes
How to test the changes in this Pull Request:
Changelog entry
Title
Detailed Description of the pull request. What was previous behaviour
and what will be changed in this PR.
Before Changes
Describe the issue before changes with screenshots(s).
After Changes
Describe the issue after changes with screenshot(s).
Feature Video (optional)
Link of detailed video if this PR is for a feature.
PR Self Review Checklist:
FOR PR REVIEWER ONLY:
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation