Skip to content

fix(e2e): replace false-passing assertions and hard-coded waits in test suite#28486

Open
dididy wants to merge 3 commits intocalcom:mainfrom
dididy:fix/e2e-clean-assertions
Open

fix(e2e): replace false-passing assertions and hard-coded waits in test suite#28486
dididy wants to merge 3 commits intocalcom:mainfrom
dididy:fix/e2e-clean-assertions

Conversation

@dididy
Copy link

@dididy dididy commented Mar 18, 2026

What does this PR do?

Fixes silent test failures caused by always-passing assertions and arbitrary waitForTimeout() calls in the Playwright E2E test suite.

This is a scoped subset of the changes from #28466, split to stay within the 10-file / 500-line PR guidelines. Files with outstanding review feedback have been excluded and will be addressed in follow-up PRs.


Problem 1: toBeTruthy() on Locator objects (always passes)

page.locator() and page.getByTestId() return a Locator object, which is always truthy regardless of whether the element exists in the DOM.

Before:

expect(await page.locator("text=This app has not been setup yet").first()).toBeTruthy();

After:

await expect(page.locator("text=This app has not been setup yet").first()).toBeVisible();

Affected files: fixtures/apps.ts, payment-apps.e2e.ts


Problem 2: isDisabled() result not asserted (no-op)

isDisabled() returns a Promise<boolean>. Without asserting the result, the call is a complete no-op.

Before:

expect(await editButton.isDisabled()).toBe(true);  // non-retrying snapshot

After:

await expect(editButton).toBeDisabled();  // Playwright auto-retry
await expect(deleteButton).toBeDisabled();

Affected files: fixtures/workflows.ts


Problem 3: toBeTruthy() on booleans and nullable values

Before:

expect(v1 === "" || /^\+\d{1,3}$/.test(v1)).toBeTruthy();
expect(cookiesMap.has("next-auth.csrf-token")).toBeTruthy();
expect(href).toBeTruthy();

After:

expect(v1 === "" || /^\+\d{1,3}$/.test(v1)).toBe(true);
expect(cookiesMap.has("next-auth.csrf-token")).toBe(true);
expect(href).not.toBeNull();

Affected files: booking-phone-autofill.e2e.ts, login.api.e2e.ts, event-types.e2e.ts, signup.e2e.ts


Problem 4: waitForTimeout() — brittle arbitrary waits

File Before After
eventType/limit-tab.e2e.ts waitForTimeout(10000) getByTestId("time").first().waitFor({ state: "visible" })
fixtures/apps.ts waitForTimeout(1000) ×2 element visibility waits
signup.e2e.ts waitForTimeout(500), waitForTimeout(1000) element / member visibility waits

Problem 5: Strict mode violation and toast assertion in out-of-office.e2e.ts

  • getByTestId("away-emoji") matched 6 elements → added .first()
  • Infinite redirect error is displayed as a toast, not inline text → getByTestId("toast-error")

Problem 6: Alby setup page navigation in payment-apps.e2e.ts

Setup page returns 500 without API keys configured, making "Connect with Alby" text unverifiable. Navigation to the setup page via waitForURL() is sufficient verification.

Also replaced no-op isDisabled() on stripe switch (pre-existing UI bug where switch never becomes disabled) with toBeChecked() on the paypal switch to verify the actual enabled state.


Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A — test-only changes.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

PLAYWRIGHT_HEADLESS=1 yarn e2e \
  apps/web/playwright/booking-phone-autofill.e2e.ts \
  apps/web/playwright/event-types.e2e.ts \
  apps/web/playwright/eventType/limit-tab.e2e.ts \
  apps/web/playwright/fixtures/apps.ts \
  apps/web/playwright/fixtures/workflows.ts \
  apps/web/playwright/login.api.e2e.ts \
  apps/web/playwright/out-of-office.e2e.ts \
  apps/web/playwright/payment-apps.e2e.ts \
  apps/web/playwright/signup.e2e.ts

Checklist

  • My code follows the style guidelines of this project
  • I have checked that my changes generate no new warnings
  • These changes are pure test quality improvements — no production code modified

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/playwright/fixtures/workflows.ts">

<violation number="1" location="apps/web/playwright/fixtures/workflows.ts:130">
P2: Disabled-state checks use non-retrying snapshot assertions instead of Playwright’s auto-retrying locator assertions, which can cause flaky E2E failures.</violation>
</file>

<file name="apps/web/playwright/out-of-office.e2e.ts">

<violation number="1" location="apps/web/playwright/out-of-office.e2e.ts:203">
P2: `getByTestId("away-emoji")` can resolve to multiple elements (one per away day), and Playwright strict mode throws when assertions target a non-unique locator. This can make the test flaky/fail even when the UI is correct.</violation>

<violation number="2" location="apps/web/playwright/out-of-office.e2e.ts:370">
P1: Custom agent: **E2E Tests Best Practices**

Rule 1 violation: these updated assertions still rely on `page.locator('text=...')` instead of resilient `getByTestId`-based selectors.</violation>
</file>

<file name="apps/web/playwright/payment-apps.e2e.ts">

<violation number="1" location="apps/web/playwright/payment-apps.e2e.ts:127">
P1: Custom agent: **E2E Tests Best Practices**

Rule 1 forbids `page.locator('text=...')`, but this change adds that brittle selector pattern across several payment-app assertions. Replace these page-level text locators with stable test ids (or another scoped stable locator) instead.</violation>

<violation number="2" location="apps/web/playwright/payment-apps.e2e.ts:210">
P1: Custom agent: **E2E Tests Best Practices**

Use `expect(page).toHaveURL()` here instead of `waitForURL()`. Rule 1 requires a URL assertion so this test fails if the setup page immediately redirects somewhere unexpected.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -123,15 +123,15 @@ test.describe("Payment app", () => {

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: Custom agent: E2E Tests Best Practices

Rule 1 forbids page.locator('text=...'), but this change adds that brittle selector pattern across several payment-app assertions. Replace these page-level text locators with stable test ids (or another scoped stable locator) instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/playwright/payment-apps.e2e.ts, line 127:

<comment>Rule 1 forbids `page.locator('text=...')`, but this change adds that brittle selector pattern across several payment-app assertions. Replace these page-level text locators with stable test ids (or another scoped stable locator) instead.</comment>

<file context>
@@ -123,15 +123,15 @@ test.describe("Payment app", () => {
-    // expect 200 sats to be displayed in page
-    expect(await page.locator("text=350").first()).toBeTruthy();
+    // expect 350 USD to be displayed in page
+    await expect(page.locator("text=350").first()).toBeVisible();
 
     await selectFirstAvailableTimeSlotNextMonth(page);
</file context>
Fix with Cubic

Copy link
Author

Choose a reason for hiding this comment

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

Two reasons these can't be replaced with data-testid selectors. AlbyPriceComponent, AppCard, and the paypal setup page have no data-testid attributes - that would need a separate PR on the production side. Also, some of these are checking the actual text on purpose: 200 sats and MX$150.00 exist to verify the price format is correct. A testid would only tell you the element is there, not what it says.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/playwright/payment-apps.e2e.ts">

<violation number="1">
P2: Custom agent: **E2E Tests Best Practices**

Avoid page-level text locators in E2E tests per E2E Tests Best Practices; use a stable selector (e.g., data-testid) or verify navigation with expect(page).toHaveURL instead.</violation>

<violation number="2">
P2: The new assertion only checks that a Locator object exists, not that the Alby setup page content appears. Because locators are always truthy, this test can pass even if navigation fails or the text never appears.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@romitg2 romitg2 marked this pull request as ready for review March 22, 2026 15:56
@romitg2 romitg2 added ready-for-e2e run-ci Approve CI to run for external contributors labels Mar 22, 2026
romitg2
romitg2 previously approved these changes Mar 22, 2026
Copy link
Member

@romitg2 romitg2 left a comment

Choose a reason for hiding this comment

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

makes sense to me, let's run ci

dididy and others added 3 commits March 23, 2026 07:19
- Replace toBeTruthy() on Locator objects with toBeVisible() (always-passing)
- Replace toBeTruthy() on boolean expressions with toBe(true) for clarity
- Replace hardcoded waitForTimeout() with element-based waits (waitFor/toBeVisible)
- Fix strict mode violation: getByTestId("away-emoji").first()
- Fix error assertion: infinite redirect error shown as toast → getByTestId("toast-error")
- Fix isDisabled() no-op → await expect().toBeDisabled() in workflows fixture
- Fix alby setup: add waitForURL(), remove unverifiable "Connect with Alby" assertion
- Fix stripe/paypal: replace no-op isDisabled() with toBeChecked() on paypal switch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- workflows.ts: replace non-retrying isDisabled() snapshot with toBeDisabled()
- out-of-office.e2e.ts: add .first() for away-emoji strict mode, use toast-error testid, remove unused const t
- payment-apps.e2e.ts: replace always-passing toBeTruthy() with toHaveURL() assertion
@dididy
Copy link
Author

dididy commented Mar 22, 2026

Sorry about the noise — CI failed on the previous run, so I rebased and force-pushed. Could you re-trigger CI?

The failures were in the API v2 roles/permissions tests (run link) — organizations-roles-permissions.controller.e2e-spec.ts returning 403 instead of 201. This PR only changes Playwright specs under apps/web/playwright/, so it shouldn't be related.

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

Labels

ready-for-e2e run-ci Approve CI to run for external contributors size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants