feat(e2e): contractor domain — scenarios + spec rewrites#1833
Conversation
85dfbda to
dd77938
Compare
|
Changes below this comment are for the API upgrade to 11-15-2025 |
dd77938 to
c767f6c
Compare
fe9d1d5 to
1538505
Compare
cd91cb8 to
a133f07
Compare
1538505 to
b784e95
Compare
1a995fd to
034555e
Compare
8f28bec to
15c4029
Compare
There was a problem hiding this comment.
⚠️ 1 New Suppressed Finding
The latest commit contains 1 new suppressed finding.
| Findings |
|---|
| CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG) Original Rule ID: rules_lgpl_javascript_crypto_rule-node-insecure-random-generator Remediation: Replace all uses of crypto.pseudoRandomBytes() and Math.random() withcrypto.randomBytes() for any security-sensitive random number generation.The crypto.randomBytes() function uses the operating system'scryptographically secure random number generator (e.g., /dev/urandom on Linux,CryptGenRandom on Windows). For generating random integers within a specificrange, use crypto.randomInt(min, max) which is available in Node.js 14.10.0and later. When generating tokens or identifiers, consider usingcrypto.randomUUID() for RFC 4122 compliant UUIDs, or encode random bytesusing base64url encoding with randomBytes(32).toString('base64url') for URL-safe tokens. Note that crypto.pseudoRandomBytes() was deprecated in Node.jsv6.0.0 and should never be used in modern applications.The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong. The application uses cryptographically weak random number generators such as crypto.pseudoRandomBytes() or Math.random() for security-sensitiveoperations. The crypto.pseudoRandomBytes() function uses a non-cryptographicPRNG that may be... |
| 📘 Learn More |
Scanner: boostsecurity - Semgrep
|
@cursoragent can we investigate? Is this issue caused by a specific change in this branch?
|
Investigation CompleteI investigated the security finding and here's what I found: Yes, this issue was caused by changes in this branch. The file What I FixedI replaced all instances of
Why This MattersWhile this is E2E test code (not production), using cryptographically secure random number generation is the right approach and eliminates the security scanner warning. The The changes have been committed and pushed to the |
There was a problem hiding this comment.
🚀 1 New Security Fix
You just committed 1 security fix. 😎 Keep up the great work!
🎯 Take a look at what findings you fixed.
| Findings |
|---|
| CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG) Original Rule ID: rules_lgpl_javascript_crypto_rule-node-insecure-random-generator The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong. The application uses cryptographically weak random number generators such as crypto.pseudoRandomBytes() or Math.random() for security-sensitiveoperations. The crypto.pseudoRandomBytes() function uses a non-cryptographicPRNG that may be... |
| 📘 Learn More |
Scanner: boostsecurity - Semgrep
Fresh Eyes ReviewFound 6 issues in this PR. PR Description Issues
Info (1)
Download findings.json — drag the file into Claude or use Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews |
e7f6c9c to
7cb9add
Compare
|
@cursoragent Are all the comments in this PR up to date and resolved? |
Summary: PR Comments ReviewI've reviewed and addressed all code-related comments in the PR. Here's the status: ✅ Resolved Issues
ℹ️ Remaining Comments (Non-blocking)The Fresh Eyes bot has feedback about the PR description (not code):
These are documentation suggestions for the PR description. Would you like me to update the PR description to address this feedback, or are you satisfied with the current description? All code issues have been resolved and pushed to the branch. |
f7509d2 to
23258c3
Compare
|
@cursoragent can you resolve conflicts here and make sure we're rebased off main? |
|
Perfect! I've successfully rebased the Rebase SummaryStrategy: Instead of a full rebase (which caused many conflicts due to commits already in main), I cherry-picked only the contractor-domain-specific commits onto the latest main. Result:
Commits included (in order):
The branch is now clean and ready for review! |
a153696 to
cd4f8df
Compare
|
From claude, with a grain of salt: 1. Inconsistent field-visibility guards in flow drivers
// Guard consistently with the lifecycle specs:
const ssnField = page.getByLabel(/social security number/i)
if (await ssnField.isVisible({ timeout: 5_000 }).catch(() => false)) {
await ssnField.fill(generateUniqueSSN())
}
// Same pattern for EIN in fillBusinessProfile2. Silent pass on empty-contractor state hides provisioning failures In // Surface the failure explicitly instead:
if (await empty.isVisible().catch(() => false)) {
test.skip(true, 'No contractors provisioned — skipping payment flow')
}Posted by Claude on behalf of Steve |
| @@ -0,0 +1,313 @@ | |||
| import { expect, type Page } from '@playwright/test' | |||
There was a problem hiding this comment.
Helpers in this file make me a little worried about how extensible this is going to be. Will every domain have a similar list of drivers?
There was a problem hiding this comment.
yeah thats a fair point. I think each domain has its own unique drivers currently. We might be able to create repeatable patterns here but right now I think AI's metric is just to get through the test and meet the need and this is what some QAs do. I don't love it either and I think we can iterate here.
|
@cursoragent Can you investigate the feedback and see if we should address Steve's cursor feedback? |
Investigation Complete ✅I investigated Steve's feedback and both concerns are valid. I've implemented fixes for both issues. Issue 1: Inconsistent Field Visibility GuardsProblem: The flow driver functions (
Fix Applied: Added visibility guards to both functions in const ssnField = page.getByLabel(/social security number/i)
if (await ssnField.isVisible({ timeout: 5_000 }).catch(() => false)) {
await ssnField.fill(generateUniqueSSN())
}Issue 2: Silent Pass on Empty Contractor State
|
6fb4950 to
0df77c6
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
Merge shared infrastructure CI stabilization, switch onboarding to a stable demo, and format the contractor payment scenario for consistent format checks. Co-authored-by: Cursor <cursoragent@cursor.com>
…enarios Adds two new contractor scenarios: - contractor-business: Business contractor (Acme Consulting LLC) so the business profile branch is reachable. - contractor-mixed-roster: Individual + business contractor list view. Adds two new specs covering each scenario plus an additional contractor-payment test that clicks New payment and asserts the payment composition surface mounts. Co-authored-by: Cursor <cursoragent@cursor.com>
contractor-onboarding `can fill out the contractor profile form` was
silently passing — it filled a first name with digits ("Jane123456")
which triggers a regex validation error that the test never noticed
because it asserted only that an `<article>` was visible. Use a clean
"Jane" / "TestContractor" name (uniqueness comes from the SSN
generator already in use) and assert the next step heading
(Address/New Hire) is visible after Create Contractor.
contractor-payment `clicking new payment` was also weak (article only).
Now asserts the "Pay contractors" composition heading and the payment
date input are both visible — proving we actually entered the create
payment surface.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds two lifecycle specs to round out contractor onboarding paths: - contractor-business-create-lifecycle: drives the Business contractor branch (business name, EIN, Fixed wage type, start date) through the profile step, asserts advancement to address or new-hire step. - contractor-cancel-lifecycle: opens the profile step from the list, uses the progress-bar "Back to contractors" CTA, and asserts return to the contractor list (covers the cancelTransition path that drops contractorId and selfOnboarding flags). Combined with the existing Individual create + back-from-profile tests, all major contractor onboarding state-machine branches now land at a terminal UI state in their video. Co-authored-by: Cursor <cursoragent@cursor.com>
Adds contractor-payment-submit-lifecycle which drives: list -> click New payment -> Pay contractors composition page -> fill payment date -> click Continue with no contractor payments entered -> assert the "Please add at least one contractor payment" validation alert is visible. This exercises the createPayment state's submit-with-empty-rows validation branch in the paymentMachine, which is a documented terminal state until a contractor row is edited with wage/hours. Co-authored-by: Cursor <cursoragent@cursor.com>
Drives the full create-payment path: - New payment → Pay contractors composition - Enter payment date - Open Edit contractor payment hamburger → menu → Edit modal - Fill wage (Fixed) or hours (Hourly) - Save → modal closes - Continue → assert payment summary heading or success alert Covers the CONTRACTOR_PAYMENT_CREATED transition from createPayment to paymentSummary terminal in the paymentMachine. Co-authored-by: Cursor <cursoragent@cursor.com>
Opens an existing Business contractor's hamburger menu, clicks Edit, and asserts the profile form renders pre-filled with Business radio checked, business name populated, and Update Contractor CTA visible. Covers the CONTRACTOR_UPDATE re-entry transition from the list state back into profile with contractorId preserved in context. Co-authored-by: Cursor <cursoragent@cursor.com>
…in flow paths
The contractor domain owns two flows reachable through the SDK
(`flow=contractor-onboarding` for both Individual and Business profile
branches, and `flow=contractor-payment` from list through Payment
Summary). Existing specs in e2e/tests/contractor/ cover landing-page
and single-screen surface checks, but no spec drives any of them all
the way to their terminal success state.
This change adds a canary suite (e2e/tests/contractor/canary/) that
walks each flow end-to-end against the demo backend and asserts on
the real terminal landmark:
- 01-onboarding-individual: Contractor list → Add → Profile (Individual,
SSN + Hourly + Start Date) → Home address → Payment method (Check) →
New hire report (No) → Submit → "Contractor has been onboarded!"
- 02-onboarding-business: Contractor list → Add → Profile (Business,
EIN + Fixed + Start Date) → Business address → Payment method (Check)
→ New hire report (No) → Submit → "Contractor has been onboarded!"
- 03-payment-create-submit: Payment list → New payment → Pay
contractors (date + edit Hours on the seeded payable Hourly
contractor, force Check) → Continue → Review and submit → Submit →
Payment Summary ("Payment group created successfully")
Implementation notes:
- One shared scenario file, e2e/scenarios/contractor/full-flow-canary.json,
provisions react_sdk_demo_company_onboarded plus a single pre-decorated
Individual Hourly contractor ("payable"). The onboarding canaries
ignore him and create their own through the UI; the payment canary
needs an onboarded contractor immediately payable, which he provides.
- Drivers live in e2e/utils/contractorFlowDrivers.ts and import only
fillDate + waitForLoadingComplete from e2e/utils/helpers.ts (no
re-implementation of shared helpers).
- The Payment Method step picks Check and the payment Edit modal forces
the Check radio to avoid fast-ACH funding submission blockers that
fresh demo companies hit on Direct Deposit.
- The New Hire Report step picks "No, I have already filed" to keep
the canary off the state-filing path.
- The Address step's State picker is a React Aria Select trigger, not a
native combobox — driven by clicking the "Select state..." button and
then the matching option, rather than getByLabel(/state/i) which
resolves to the hidden combobox element.
All 3 specs verified PASSED against demo (workers=1, matching CI's
serial mode): 3 passed (55.8s).
Videos available locally at ~/Desktop/contractor-videos/canary-*-PASSED.webm
for the PR description.
Co-authored-by: Cursor <cursoragent@cursor.com>
…eneration Co-authored-by: Jeffrey D Johnson <jeffredodd@users.noreply.github.com>
…queEIN Co-authored-by: Jeffrey D Johnson <jeffredodd@users.noreply.github.com>
…ailure - Add visibility guards to SSN/EIN fields in contractorFlowDrivers - Replace silent pass with test.skip() when no contractors provisioned - Ensures canary and lifecycle tests handle optional fields consistently - Makes CI provisioning failures visible instead of silently passing
The contractor payment canary's setPaymentDate helper used today + 14d as
the payment date with a "clear weekend rules" comment, but it didn't
account for US federal holidays. CI run on 2026-05-20 produced today + 14d
= Mon May 25, 2026 = Memorial Day. The demo backend rejected it with a
non-actionable form alert ("Mon, May 25th is not a business day"), the
spec couldn't reach Review and submit, and the 8-min test timeout fired
3x in a row.
Adds a reusable nextBusinessDay(from, minOffsetDays) helper in
e2e/utils/helpers.ts that returns the first weekday after the offset that
is also not a US federal holiday. Encoded 2026-2027 holidays with a TODO
to refresh annually. Operates in UTC so results are stable regardless of
the runner timezone.
setPaymentDate now calls nextBusinessDay(new Date(), 14). Future payment
drivers can reuse the helper from helpers.ts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two related fixes for the contractor payment canary (spec 03): 1. Drop the `payable` Casey Canary decoration from contractor/full-flow-canary.json. The scenario runner's contractor decoration only calls POST /contractors with profile fields; it does not set an address, payment method, or mark onboarding complete. As a result the decorated contractor never appeared in "Pay contractors" on fresh demo provisions. The base demo react_sdk_demo_company_onboarded already includes payment-ready contractors (Ella Fitzgerald, Louis Armstrong), so the canary scenario no longer needs to add one. 2. Add an empty-state guard at the top of editFirstContractorPayment in contractorFlowDrivers.ts. Previously, if "No contractors available for payment" rendered, the spec silently sat at its 8-min test timeout waiting for an Edit button that would never appear (this is how the failing CI run burned 17.7m across 3 retries). The driver now probes the empty-state text with a 3s timeout and throws a clear, actionable error if it sees it, then explicitly waits up to LONG_WAIT for the Edit button before clicking. Also drops the stale "decorated payable contractor is Hourly" comment; the driver already handles both Hourly and Fixed wage types via the hoursField / wageField fallback. As a side benefit, since all 3 canary specs share contractor/full-flow-canary, the worker-scoped scenario cache now collapses provisioning for the whole canary suite into a single demo creation per worker. Co-authored-by: Cursor <cursoragent@cursor.com>
Mirror the slimming that landed with the employee + payroll domain PRs:
collapse what was 5 contractor scenarios down to 4 by merging
overlapping shapes and dropping decorations the specs don't actually
read. The remaining set is the minimum needed to keep each spec working.
- Delete contractor/contractor-business.json. The business-profile branch
is now covered by contractor-mixed-roster, which already provisions an
individual + business contractor pair. The business contractor in the
shared roster is renamed from "Bright Services Inc" to
"Acme Consulting LLC" so contractor-business.spec.ts and
contractor-onboarding-edit-lifecycle.spec.ts (which both assert on the
Acme name) can target the shared roster instead of a dedicated scenario.
- Drop the Charlie decoration from contractor/contractor-payment.json.
The recent fix to rely on the demo's payable contractors
(Ella Fitzgerald, Louis Armstrong) instead of decorated ones already
made the contractor decoration dead weight here — the scenario runner
only POSTs profile fields, so the decorated contractor never appears in
"Pay contractors" anyway. The scenario stays as a distinct id from
contractor/full-flow-canary so the canary suite's mutating tests remain
cache-isolated from these read-only lifecycle specs.
- Update consumer specs:
- contractor-business.spec.ts → contractor/contractor-mixed-roster
- contractor-onboarding-edit-lifecycle.spec.ts → contractor/contractor-mixed-roster
- contractor-mixed-roster.spec.ts → assert "Acme Consulting" + "Charlie"
(instead of "Bright Services" + "Charlie")
Net effect on a fresh CI run: contractor-domain provisioning drops from
5 demo companies (with 4 decorated contractors total) to 4 demo companies
(with 2 decorated contractors total). Both contractor-onboarding and
contractor-payment scenarios provision no contractors at all now, so
their provisioning collapses to a single demo creation per worker — the
same shape the payroll-domain biweekly-shared scenario uses.
Co-authored-by: Cursor <cursoragent@cursor.com>
05e9217 to
e1272f1
Compare
| await page.goto('/?flow=contractor-payment') | ||
| await waitForLoadingComplete(page, 60000) | ||
|
|
||
| await page | ||
| .getByRole('button', { name: /new payment/i }) | ||
| .first() | ||
| .click() | ||
| await waitForLoadingComplete(page, 60000) | ||
|
|
||
| const composition = page.getByRole('heading', { name: /pay contractors/i }) | ||
| const empty = page.getByRole('heading', { name: /no contractors available/i }) | ||
| await expect(composition.or(empty)).toBeVisible({ timeout: 30000 }) | ||
|
|
||
| if (await empty.isVisible().catch(() => false)) { | ||
| test.skip(true, 'No contractors provisioned — skipping payment flow') | ||
| return | ||
| } | ||
|
|
||
| const dateInput = page.getByLabel(/payment date/i) | ||
| await expect(dateInput).toBeVisible() | ||
| const futureDate = new Date() | ||
| futureDate.setDate(futureDate.getDate() + 7) | ||
| await dateInput.fill(futureDate.toISOString().slice(0, 10)) |
There was a problem hiding this comment.
🟡 Minor | code-duplication
Payment setup sequence duplicated with contractor-payment-submit-lifecycle.spec.ts (lines 19-41 are nearly identical: goto, click new payment, empty-state skip, fill date). The PR already introduces shared helpers for this exact flow in contractorFlowDrivers.ts (landOnPaymentList, startNewPayment, setPaymentDate) but the lifecycle specs inline the logic instead of reusing them. The inlined version also uses a naive +7 day offset that may hit holidays, while the driver uses the more robust nextBusinessDay helper.
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
| await dateInput.fill(futureDate.toISOString().slice(0, 10)) | ||
|
|
||
| const editTrigger = page.getByRole('button', { name: /edit contractor payment/i }).first() |
There was a problem hiding this comment.
🟠 Major | logic-errors
Payment date uses naive new Date() + 7 days which can land on a weekend or US holiday, causing the backend to reject it. The canary driver (contractorFlowDrivers.ts setPaymentDate) explicitly uses nextBusinessDay(new Date(), 14) to avoid this exact failure mode — its comment states: 'The demo backend rejects e.g. Memorial Day with "Mon, May 25th is not a business day"'. This spec goes all the way to submission, so a non-business date will cause intermittent CI failures (~28% chance of hitting a weekend alone).
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
| '2026-05-25', // Memorial Day | ||
| '2026-06-19', // Juneteenth | ||
| '2026-07-03', // Independence Day (observed, July 4 is Saturday) | ||
| '2026-09-07', // Labor Day | ||
| '2026-10-12', // Columbus Day | ||
| '2026-11-11', // Veterans Day | ||
| '2026-11-26', // Thanksgiving | ||
| '2026-11-27', // Day after Thanksgiving (banking holiday) | ||
| '2026-12-25', // Christmas Day | ||
| '2027-01-01', // New Year's Day | ||
| '2027-01-18', // MLK Day | ||
| '2027-02-15', // Presidents Day | ||
| '2027-05-31', // Memorial Day | ||
| '2027-06-18', // Juneteenth (observed, June 19 is Saturday) | ||
| '2027-07-05', // Independence Day (observed, July 4 is Sunday) | ||
| '2027-09-06', // Labor Day | ||
| '2027-10-11', // Columbus Day | ||
| '2027-11-11', // Veterans Day | ||
| '2027-11-25', // Thanksgiving | ||
| '2027-11-26', // Day after Thanksgiving | ||
| '2027-12-24', // Christmas Day (observed, Dec 25 is Saturday) | ||
| ]) | ||
|
|
||
| function toIsoDate(date: Date): string { | ||
| return date.toISOString().slice(0, 10) | ||
| } | ||
|
|
||
| function isBusinessDay(date: Date): boolean { | ||
| const day = date.getUTCDay() | ||
| if (day === 0 || day === 6) return false | ||
| return !US_FEDERAL_HOLIDAYS_ISO.has(toIsoDate(date)) | ||
| } | ||
|
|
||
| /** | ||
| * Return the first business day (weekday + not a US federal holiday) that is | ||
| * at least `minOffsetDays` after `from`. Operates in UTC to keep results | ||
| * stable regardless of the test runner's local timezone — the demo backend | ||
| * also validates against UTC dates. | ||
| * | ||
| * Designed for payment / pay-date pickers where the backend rejects | ||
| * non-business days. Without this, a +14 offset that lands on Memorial Day | ||
| * surfaces as a non-actionable form-validation alert in CI. | ||
| */ | ||
| export function nextBusinessDay(from: Date, minOffsetDays: number): Date { | ||
| const candidate = new Date( | ||
| Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), from.getUTCDate() + minOffsetDays), | ||
| ) | ||
| while (!isBusinessDay(candidate)) { | ||
| candidate.setUTCDate(candidate.getUTCDate() + 1) | ||
| } | ||
| return candidate | ||
| } | ||
|
|
||
| export async function waitForLoadingComplete(page: Page, timeout = 30_000) { | ||
| const suspenseFallback = page.getByRole('region', { name: /^loading/i }) |
There was a problem hiding this comment.
🟡 Minor | test-coverage
nextBusinessDay() contains non-trivial date logic (weekend detection, US federal holiday exclusion, UTC date advancement) with no unit test. A bug in the holiday set or the advancement loop would surface only as a flaky CI failure on a specific calendar date. Consider adding a unit test covering: offset landing on a weekend, offset landing on a holiday, and consecutive non-business days.
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews


Summary
Videos
canary-03-payment-create-submit-PASSED.webm
canary-02-onboarding-business-PASSED.webm
canary-01-onboarding-individual-PASSED.webm
Base: PR #1829 (e2e/infrastructure)
Notion tasks: #74-079
Made with Cursor