Skip to content

t080/t081: Add Playwright E2E tests for automations system#613

Merged
superdav42 merged 18 commits intomainfrom
feature/automations-e2e
Mar 24, 2026
Merged

t080/t081: Add Playwright E2E tests for automations system#613
superdav42 merged 18 commits intomainfrom
feature/automations-e2e

Conversation

@superdav42
Copy link
Copy Markdown
Contributor

@superdav42 superdav42 commented Mar 19, 2026

Summary

  • Adds tests/e2e/automations.spec.js with 30 tests across 3 describe blocks covering the full automations UI surface
  • All REST calls are intercepted via page.route() mocks — no real AI provider or live back-end required
  • Follows the same patterns as existing E2E specs (floating-widget.spec.js, admin-page.spec.js)

Test coverage

Scheduled Automations (t080)

  • Manager heading renders on Automations tab
  • Existing automations appear in the card list
  • Card shows schedule badge and run count
  • Add Automation button opens creation form
  • Create button disabled when Name or Prompt is empty
  • Creating an automation submits POST and shows success notice
  • Enable/disable toggle calls PATCH with { enabled: false }
  • Disabled automation card has ai-agent-skill-card--disabled class
  • Cancel button hides the form
  • Schedule select has all four WP cron options
  • Empty state shows Quick Start Templates when no automations exist
  • Use Template button pre-fills the form

Event-Driven Automations (t081)

  • Manager heading renders on Events tab
  • Existing event automations appear in the card list
  • Card shows hook name badge and run count
  • Add Event button opens creation form
  • Create button disabled until Name + hook + prompt are all filled
  • Creating an event automation submits POST and shows success notice
  • Trigger hook select is populated from /event-triggers endpoint
  • Selecting a trigger shows its description and available placeholders
  • Enable/disable toggle calls PATCH with { enabled: false }
  • Disabled event card has ai-agent-skill-card--disabled class
  • Empty state message when no event automations exist
  • Cancel button hides the form

Proactive Alert Badge on FAB

  • Badge hidden when alert count is 0
  • Badge shows numeric count when alerts exist
  • Badge shows "9+" when count exceeds 9
  • Badge has accessible aria-label with the count

Mocking strategy

mockAutomationRoutes(page, overrides) installs a single page.route() handler that intercepts all /wp-json/gratis-ai-agent/v1/* requests and returns controlled JSON. Individual tests can override specific endpoints (e.g. to simulate a POST response or a disabled automation) without affecting other routes.

Closes #609

Summary by CodeRabbit

  • Tests

    • Added comprehensive E2E coverage for scheduled and event-driven automations: creation flows, form validation, enable/disable toggles, templates, empty states, run counts and UI rendering.
    • Added E2E coverage for the proactive FAB alert badge: visibility, count display (clamped to "9+"), and accessible label.
    • Added stable mocked responses and navigation helpers to make automations tests more reliable.
    • Improved admin-page test timing for collapse/expand behavior.
  • Bug Fixes

    • Events manager now correctly renders placeholders and conditions when provided as arrays or objects.

Adds tests/e2e/automations.spec.js with 30 tests across 3 describe blocks:

- Scheduled Automations (t080): list display, create form validation,
  POST submission with success notice, enable/disable toggle (PATCH),
  disabled card CSS class, empty state, template quick-start, cancel
- Event-Driven Automations (t081): list display, create form validation,
  POST submission, trigger hook select population, trigger info/placeholders,
  enable/disable toggle (PATCH), disabled card CSS class, empty state, cancel
- Proactive Alert Badge on FAB: hidden at count=0, shows count, shows 9+
  for counts >9, accessible aria-label

All REST calls are intercepted via page.route() mocks so tests run without
a real AI provider or live WordPress back-end.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Playwright E2E suite for Gratis AI Agent automations (scheduled and event-driven) with route-mocking and navigation helpers; adjusts an admin-page E2E timing wait; and updates EventsManager rendering to accept placeholder/condition arrays or objects.

Changes

Cohort / File(s) Summary
Automations E2E Tests
tests/e2e/automations.spec.js
New ~1.2k-line Playwright suite with shared mock datasets, mockAutomationRoutes(page, overrides) interceptor, goToAutomationsTab/goToEventsTab helpers, tests for scheduled/event-driven automation create flows, enable/disable toggles, templates, empty states, and FAB alert badge behavior.
Admin page test tweak
tests/e2e/admin-page.spec.js
Added an explicit wait asserting the first .ai-agent-abilities-category-body becomes not visible (<=5s) after collapsing, before clicking Expand, to stabilize the Collapse/Expand test.
Events manager rendering
src/settings-page/events-manager.js
Normalized selectedTrigger.placeholders and selectedTrigger.conditions to handle arrays or objects by deriving a keys array; render placeholder/condition sections only when keys exist and format placeholders as {{key}}.

Sequence Diagram(s)

sequenceDiagram
  participant Test as Playwright Test
  participant Page as Browser Page
  participant API as Gratis AI Agent API
  Test->>Page: goToAutomationsTab()
  Page->>API: GET /settings (bootstrap)
  API-->>Page: settings JSON
  Page->>API: GET /automations
  API-->>Page: automations list
  Test->>Page: open "Add Automation" form
  Page->>API: POST /gratis-ai-agent/v1/automations (create)
  API-->>Page: created automation
  Page->>API: GET /gratis-ai-agent/v1/automations (refresh)
  API-->>Page: updated automations list
  Page-->>Test: UI shows created automation card
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

enhancement

Poem

🐇 I hopped through mocks and tabs tonight,

I clicked the Add and watched the POST take flight,
Badges lit, toggles flipped, cards in a row,
A tiny rabbit test, green checks in tow,
Hooray — the automations hum and glow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Minor out-of-scope change detected: admin-page.spec.js adds a synchronization assertion unrelated to the automations system tests; however, events-manager.js changes align with supporting the new automations UI tests. Clarify whether the admin-page.spec.js synchronization change is a prerequisite fix for automations tests or should be submitted separately; confirm events-manager.js placeholder/condition handling supports automations test requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly describes the main change: adding Playwright E2E tests for the automations system covering tasks t080 and t081.
Linked Issues check ✅ Passed The pull request comprehensively addresses all coding requirements from linked issue #609: adds automations.spec.js with 30 E2E tests covering scheduled automations (t080), event-driven automations (t081), creation flows, enable/disable toggles, proactive alert FAB badge, and mocked REST endpoints.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/automations-e2e

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

SettingsApp blocks rendering until /settings, /providers, /abilities,
and /settings/google-analytics all respond. The original mockAutomationRoutes
only mocked automations-specific endpoints, leaving the settings page stuck
in a loading spinner — causing all 20 automations tests to fail with
'element(s) not found' because the tab content was never rendered.

Fixes:
- Add MOCK_SETTINGS constant and mock /settings, /providers, /abilities,
  /settings/google-analytics in mockAutomationRoutes
- Replace waitForLoadState('networkidle') with explicit waitFor on the
  manager container (.ai-agent-automations-manager / .ai-agent-events-manager)
  to guard against networkidle resolving before React finishes rendering
- Replace deprecated text= selector with page.getByText() in the empty
  state test for event automations
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/automations.spec.js`:
- Around line 567-582: The test is asserting the wrong CSS modifier: the card
markup emits the class "is-disabled" (set in
src/settings-page/automations-manager.js) not "ai-agent-skill-card--disabled";
update the assertion in the test (the test named 'disabled automation card has
disabled CSS class' which locates '.ai-agent-skill-card' and filters by
disabledAutomation.name) to expect the "is-disabled" class (e.g. change the
regex /ai-agent-skill-card--disabled/ to match /is-disabled/) and make the same
change for the other occurrence referenced in the comment (lines ~895-907).
- Around line 521-565: The test is targeting the wrong element (checkbox) and
not proving the UI lifecycle; update the route handler and assertions so the
test clicks the card action Enable/Disable button (select the button in the card
actions instead of input[type="checkbox"] — use the same card locator that
filters by MOCK_AUTOMATION.name), capture the PATCH as you do now, then have the
subsequent GET response returned by the route reflect enabled: false (modify the
route.fulfill GET branch to return a copy of MOCK_AUTOMATION with enabled: false
after the PATCH is observed), and finally assert the button text/state flips
(e.g., expect the action button to show “Enable”/“Disable” accordingly) after
poll/wait for fetchAll; apply the identical selector and GET-refresh change to
the other test at lines 852-893 as well.
- Around line 468-519: The test currently only asserts that a POST occurred and
a success notice appeared; tighten it by validating the POST request body and
the rendered list item: in the page.route handler for
'**/wp-json/gratis-ai-agent/v1/automations' inspect route.request().postData()
when method === 'POST' and assert it contains the expected fields (e.g., name:
'My New Automation', prompt: 'Do something useful.' and any schedule fields),
then still fulfill with newAutomation; after the POST and notice, also assert
the list actually renders the created item (e.g., locate the automations list
and expect text 'My New Automation' toBeVisible) so you cover form serialization
and list refresh (apply same pattern to the event test for hook_name and
prompt_template).
- Around line 277-283: The current route mock only matches '/automation-logs' so
requests to the registered endpoint '/automations/{id}/logs' fall through;
update the condition in tests/e2e/automations.spec.js (the branch that calls
route.fulfill) to detect the registered logs path (e.g., match '/automations/'
and '/logs' or use a regex like /\/automations\/\d+\/logs/) and return the same
JSON empty-array response via route.fulfill so real logs requests are properly
mocked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0ffd181-989a-4fe5-889e-026cb138b2b5

📥 Commits

Reviewing files that changed from the base of the PR and between 6162fdf and 8e3132d.

📒 Files selected for processing (1)
  • tests/e2e/automations.spec.js

Comment thread tests/e2e/automations.spec.js Outdated
Comment thread tests/e2e/automations.spec.js
Comment thread tests/e2e/automations.spec.js
Comment on lines +567 to +582
test( 'disabled automation card has disabled CSS class', async ( {
page,
} ) => {
const disabledAutomation = { ...MOCK_AUTOMATION, enabled: false };
await mockAutomationRoutes( page, {
automations: [ disabledAutomation ],
} );

await goToAutomationsTab( page );

const card = page
.locator( '.ai-agent-skill-card' )
.filter( { hasText: disabledAutomation.name } );

await expect( card ).toHaveClass( /ai-agent-skill-card--disabled/ );
} );
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Match the current disabled class name.

src/settings-page/automations-manager.js adds is-disabled when an automation is off. ai-agent-skill-card--disabled is never emitted by the current card markup, so both assertions are checking the wrong modifier.

🔧 Suggested fix
-		await expect( card ).toHaveClass( /ai-agent-skill-card--disabled/ );
+		await expect( card ).toHaveClass( /\bis-disabled\b/ );

Also applies to: 895-907

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/automations.spec.js` around lines 567 - 582, The test is asserting
the wrong CSS modifier: the card markup emits the class "is-disabled" (set in
src/settings-page/automations-manager.js) not "ai-agent-skill-card--disabled";
update the assertion in the test (the test named 'disabled automation card has
disabled CSS class' which locates '.ai-agent-skill-card' and filters by
disabledAutomation.name) to expect the "is-disabled" class (e.g. change the
regex /ai-agent-skill-card--disabled/ to match /is-disabled/) and make the same
change for the other occurrence referenced in the comment (lines ~895-907).

wp-env uses plain permalinks by default, so apiFetch resolves REST
endpoints as /index.php?rest_route=/gratis-ai-agent/v1/... rather than
/wp-json/gratis-ai-agent/v1/.... The glob pattern '**/wp-json/...' only
matches pretty-permalink URLs, causing all 20 automations tests to fail
because page.route() never intercepted the actual requests.

Fix: replace all page.route() glob strings with regex patterns that
match the path segment regardless of URL prefix:
- Catch-all: /gratis-ai-agent\/v1\// (matches both URL forms)
- Specific endpoints: /gratis-ai-agent\/v1\/automations(?:$|\?|&)/
  etc. (matches exact endpoint without ID suffix)

This is the same pattern used by the existing passing E2E specs
(chat-interactions.spec.js, chat-upload.spec.js) which use regex
patterns like /gratis-ai-agent\/v1\/stream/.
The floating widget mounts on every wp-admin page load and immediately
calls fetchAlerts(), fetchProviders(), fetchSessions(), and fetchSettings().
When loginToWordPress() navigated to /wp-admin/ after login, these requests
fired before mockAutomationRoutes() was called, hitting the real backend.

For automations/events tab tests: the real /automations endpoint returned
an empty list, so the Quick Start Templates section rendered instead of
automation cards. Template cards lack .ai-agent-skill-badge, causing 16
test failures.

For FAB badge tests: the real /alerts endpoint returned { count: 1 },
polluting the store before the test-specific mock could take effect.

Fix: call mockAutomationRoutes() before loginToWordPress() in all three
describe blocks. Playwright page.route() handlers persist across navigations
and are evaluated LIFO, so test-specific overrides registered later still
take precedence over the beforeEach default.
Automations/Events tests (16 failures):
- Add page.unrouteAll() at start of mockAutomationRoutes() so re-registering
  in beforeEach or test body replaces stale handlers rather than stacking them
- Switch test-specific route matchers from regex to function matchers to
  precisely target /automations and /event-automations list endpoints without
  matching /automations/ID or /automation-templates substrings
- Fix MOCK_TRIGGER.description to match real EventTriggerRegistry value so
  the trigger-description test passes whether mock or real server responds
- Fix empty state test: pass templates:[] override and use scoped selector
  .ai-agent-automations-manager .ai-agent-skill-cards (count 0) instead of
  global .ai-agent-skill-card (which also matches template cards)
- Fix Schedule select test: use option[value=] attribute selectors instead
  of :has-text() to avoid substring matches (Daily matching Twice Daily)

FAB badge tests (4 failures):
- Already fixed by b13d2a6 (mock before login); unrouteAll() ensures
  test-body overrides fully replace the beforeEach default mock

Flaky test (admin-page.spec.js):
- Expand all: wait for first category body to be hidden after Collapse all
  before clicking Expand all, then wait for first body to be visible before
  counting — prevents race between React state update and DOM visibility check
- Fix logs route mock to match /automations/{id}/logs (not just /automation-logs)
- Add POST body validation and list-refresh assertion to create automation tests
- Add GET-refresh mock in toggle tests so card state reflects enabled:false after PATCH
- CSS class assertion (ai-agent-skill-card--disabled) was already correct per source
Take main's 10s timeout and conditional-render comment for the
'Expand all' test — strictly better than the feature branch's 5s
timeout, fixes a real CI flake where count() returns 0 before
conditionally-rendered bodies appear in the DOM.
Add await expect(card).toBeVisible({ timeout: 10_000 }) before
accessing .ai-agent-skill-badge and input[type=checkbox] sub-elements
in automation and event card tests. The automations list is populated
asynchronously after the manager container renders; without an explicit
wait the sub-element locator resolves to 0 elements immediately,
causing 'element(s) not found' failures that cascade into login
timeouts on retry.
…aded state

- Replace regex route matcher with glob pattern '**/gratis-ai-agent/v1/**'
  so mocks intercept both pretty-permalink and plain-permalink URL formats
- Change MOCK_AUTOMATION.name to 'Test Scheduled Automation' (unique, not
  matching any built-in template name) to prevent filter() finding template
  cards during the loading state when automations=[]
- Change MOCK_EVENT.name to 'Test Event Automation' (same reason)
- Fix MOCK_TRIGGER.placeholders/conditions to key-value object format
  matching the real EventTriggerRegistry API response shape
- Fix EventsManager to normalise placeholders/conditions from key-value
  objects (real API) or arrays (mock format) before rendering
- Replace function matchers in test-specific routes with glob patterns
- Add wait for 'Loading...' text to disappear in goToAutomationsTab and
  goToEventsTab so assertions run after fetchAll() completes, not during
  the loading state where automations=[] and template cards may show
…bility

The glob pattern '**/gratis-ai-agent/v1/**' does not match wp-env's
plain-permalink URL format (?rest_route=%2Fgratis-ai-agent%2Fv1%2F...)
because the plugin path appears in the query string, not the URL path.

Switch all page.route() calls to '**' and decode the URL before matching
inside the handler. This makes mocks work with both pretty-permalink
(/wp-json/...) and plain-permalink (?rest_route=...) URL formats.

Fixes 19 failing FAB badge tests where the real backend was responding
with count=1 instead of the mocked values (count=0/3/12/2).
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/settings-page/events-manager.js (2)

309-311: Array handling assumes .key property exists on each item.

When the data is an array (test mocks), the code assumes each item has a .key property. If an item lacks this property, undefined would appear in the rendered output (e.g., {{undefined}}). Since the real API returns objects (per the PHP snippets), this only affects test mocks—but consider adding a filter for robustness.

🛡️ Optional: Filter out undefined keys
-								const keys = Array.isArray( ph )
-									? ph.map( ( p ) => p.key )
+								const keys = Array.isArray( ph )
+									? ph.map( ( p ) => p.key ).filter( Boolean )
									: Object.keys( ph || {} );

Also applies to: 329-331

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/settings-page/events-manager.js` around lines 309 - 311, The code builds
keys from ph using Array.isArray(ph) ? ph.map(p => p.key) : Object.keys(ph ||
{}), which assumes array items have a .key and can produce undefined; update the
array branch to map and then filter out falsy/undefined keys (e.g., ph.map(p =>
p.key).filter(Boolean)) or safely extract a fallback (p.key ?? p.name) as
appropriate so rendered output never contains undefined; apply the same
defensive change to the other similar keys extraction block that currently
mirrors this logic.

303-343: Consider extracting the normalization logic into a helper to reduce duplication.

The IIFE pattern works but the nearly identical logic for placeholders and conditions could be consolidated. This is a minor readability improvement.

♻️ Optional: Extract a reusable helper
+// Helper to normalise API data (object) or test mocks (array with .key).
+function normalizeKeys( data ) {
+	if ( ! data ) return [];
+	return Array.isArray( data )
+		? data.map( ( item ) => item.key )
+		: Object.keys( data );
+}
+
 // Inside the JSX, replace the IIFEs:
-							{ ( () => {
-								// The REST API returns placeholders as a
-								// key→label object; normalise to an array of
-								// keys so the component works with both the
-								// real API and array-format mocks.
-								const ph = selectedTrigger.placeholders;
-								const keys = Array.isArray( ph )
-									? ph.map( ( p ) => p.key )
-									: Object.keys( ph || {} );
-								return keys.length > 0 ? (
-									<p className="description">
-										<strong>
-											{ __(
-												'Available placeholders:',
-												'gratis-ai-agent'
-											) }
-										</strong>{ ' ' }
-										{ keys
-											.map( ( k ) => `{{${ k }}}` )
-											.join( ', ' ) }
-									</p>
-								) : null;
-							} )() }
-							{ ( () => {
-								// Same normalisation for conditions.
-								const cond = selectedTrigger.conditions;
-								const keys = Array.isArray( cond )
-									? cond.map( ( c ) => c.key )
-									: Object.keys( cond || {} );
-								return keys.length > 0 ? (
-									<p className="description">
-										<strong>
-											{ __(
-												'Available conditions:',
-												'gratis-ai-agent'
-											) }
-										</strong>{ ' ' }
-										{ keys.join( ', ' ) }
-									</p>
-								) : null;
-							} )() }
+							{ ( () => {
+								const keys = normalizeKeys( selectedTrigger.placeholders );
+								return keys.length > 0 ? (
+									<p className="description">
+										<strong>
+											{ __( 'Available placeholders:', 'gratis-ai-agent' ) }
+										</strong>{ ' ' }
+										{ keys.map( ( k ) => `{{${ k }}}` ).join( ', ' ) }
+									</p>
+								) : null;
+							} )() }
+							{ ( () => {
+								const keys = normalizeKeys( selectedTrigger.conditions );
+								return keys.length > 0 ? (
+									<p className="description">
+										<strong>
+											{ __( 'Available conditions:', 'gratis-ai-agent' ) }
+										</strong>{ ' ' }
+										{ keys.join( ', ' ) }
+									</p>
+								) : null;
+							} )() }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/settings-page/events-manager.js` around lines 303 - 343, The JSX
duplicates normalization logic for selectedTrigger.placeholders and
selectedTrigger.conditions; extract a small helper (e.g., normalizeKeys or
getKeysFromField) that accepts a field value and returns an array of keys by
handling both Array (map p => p.key or c => c.key) and object/map
(Object.keys(field || {})), then replace the two IIFE blocks with calls to that
helper (use normalizeKeys(selectedTrigger.placeholders) and
normalizeKeys(selectedTrigger.conditions)) so the UI rendering code just checks
keys.length and renders the joined strings as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/settings-page/events-manager.js`:
- Around line 309-311: The code builds keys from ph using Array.isArray(ph) ?
ph.map(p => p.key) : Object.keys(ph || {}), which assumes array items have a
.key and can produce undefined; update the array branch to map and then filter
out falsy/undefined keys (e.g., ph.map(p => p.key).filter(Boolean)) or safely
extract a fallback (p.key ?? p.name) as appropriate so rendered output never
contains undefined; apply the same defensive change to the other similar keys
extraction block that currently mirrors this logic.
- Around line 303-343: The JSX duplicates normalization logic for
selectedTrigger.placeholders and selectedTrigger.conditions; extract a small
helper (e.g., normalizeKeys or getKeysFromField) that accepts a field value and
returns an array of keys by handling both Array (map p => p.key or c => c.key)
and object/map (Object.keys(field || {})), then replace the two IIFE blocks with
calls to that helper (use normalizeKeys(selectedTrigger.placeholders) and
normalizeKeys(selectedTrigger.conditions)) so the UI rendering code just checks
keys.length and renders the joined strings as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2b61dea-1c53-47d9-9ead-4d4c73f4ad12

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4d598 and d4c8fa9.

📒 Files selected for processing (3)
  • src/settings-page/events-manager.js
  • tests/e2e/admin-page.spec.js
  • tests/e2e/automations.spec.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/admin-page.spec.js
  • tests/e2e/automations.spec.js

…eEach mock

route.continue() sends the request directly to the network, bypassing any
remaining Playwright route handlers. The toggle tests register a secondary
'**' handler that calls route.continue() for non-matching requests, which
caused the beforeEach mock to be skipped — the real server returned an empty
automations list and the card was never rendered.

Switching to route.fallback() passes the request to the next matching handler
(the beforeEach mock) so the mock data is served correctly.
…oxes

WordPress ToggleControl renders its underlying <input type="checkbox"> with
opacity:0 (visually hidden behind the styled track/thumb). Playwright's
native click() fires pointer events but does not reliably trigger React's
synthetic onChange handler on opacity:0 inputs in CI. Using
evaluate(el => el.click()) fires a programmatic HTMLElement.click() which
toggles the checked state and dispatches the change event that React's
event delegation processes, ensuring the PATCH request fires.

Fixes the two failing toggle tests:
- 'enable/disable toggle calls PATCH and updates card state' (line 603)
- 'enable/disable toggle calls PATCH for event automation' (line 983)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/e2e/automations.spec.js (2)

888-890: Use route.fallback() here as well for consistency.

Same issue as the scheduled automation creation test—this handler should use route.fallback() to pass non-matching requests to the beforeEach mock rather than sending them directly to the network.

♻️ Suggested fix
 			if (
 				! decodedUrl.includes( 'gratis-ai-agent/v1/event-automations' ) ||
 				/gratis-ai-agent\/v1\/event-automations\/\d/.test( decodedUrl )
 			) {
-				return route.continue();
+				return route.fallback();
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/automations.spec.js` around lines 888 - 890, Replace the call to
route.continue() in the request handler with route.fallback() so non-matching
requests are delegated to the beforeEach mock instead of going straight to the
network; locate the handler that currently returns route.continue() (the same
pattern used in the scheduled automation creation test) and change that single
call to route.fallback() to ensure consistent fallback behavior.

549-551: Use route.fallback() instead of route.continue() for consistency.

The toggle tests at lines 620 and 1008 correctly use route.fallback() so non-matching requests pass to the beforeEach mock. However, this handler uses route.continue(), which sends requests directly to the network and bypasses the global mock. For full test isolation and consistency with the toggle tests, use route.fallback().

♻️ Suggested fix
 			if (
 				! decodedUrl.includes( 'gratis-ai-agent/v1/automations' ) ||
 				decodedUrl.includes( '/automation-templates' ) ||
 				/gratis-ai-agent\/v1\/automations\/\d/.test( decodedUrl )
 			) {
-				return route.continue();
+				return route.fallback();
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/automations.spec.js` around lines 549 - 551, Change the request
handler that currently returns route.continue() to use route.fallback() so
non-matching requests are delegated to the shared beforeEach mock (matching the
toggle tests). Locate the handler that calls route.continue() in the automations
spec (the function handling mocked routes) and replace that return with
route.fallback(); ensure the semantics remain the same otherwise so tests use
the global mock instead of going to the network.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/automations.spec.js`:
- Around line 888-890: Replace the call to route.continue() in the request
handler with route.fallback() so non-matching requests are delegated to the
beforeEach mock instead of going straight to the network; locate the handler
that currently returns route.continue() (the same pattern used in the scheduled
automation creation test) and change that single call to route.fallback() to
ensure consistent fallback behavior.
- Around line 549-551: Change the request handler that currently returns
route.continue() to use route.fallback() so non-matching requests are delegated
to the shared beforeEach mock (matching the toggle tests). Locate the handler
that calls route.continue() in the automations spec (the function handling
mocked routes) and replace that return with route.fallback(); ensure the
semantics remain the same otherwise so tests use the global mock instead of
going to the network.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fe7abb72-2117-4893-95c1-adf1a5038e08

📥 Commits

Reviewing files that changed from the base of the PR and between d4c8fa9 and 9de52dd.

📒 Files selected for processing (1)
  • tests/e2e/automations.spec.js

…on opacity-0 checkbox

The ToggleControl's underlying <input type="checkbox"> has opacity:0 and is
a controlled React component. Both Playwright's native click() and
evaluate(el.click()) fail to trigger React's synthetic onChange handler in
CI because React's controlled-input guard prevents the browser from changing
the checked state on a controlled input.

The reliable fix is to:
1. Use the native HTMLInputElement.prototype.checked setter to bypass
   React's controlled-input guard and set checked=false
2. Dispatch a bubbling 'change' event so React's event delegation picks
   it up and calls the ToggleControl's onChange handler

This causes handleToggle() to fire the PATCH request as expected.
The ToggleControl component renders an opacity:0 checkbox inside a
.components-form-toggle wrapper. Previous attempts tried to manipulate
the hidden checkbox directly (native setter, evaluate click, change
events) but none reliably triggered React's onChange handler.

Fix: click the .components-form-toggle wrapper element — the visible
toggle track/thumb that real users interact with. This triggers the
checkbox change via standard HTML label-input association, which React's
event delegation picks up to call handleToggle → PATCH.

Also switch state assertions from toBeChecked() on the hidden input to
toHaveClass(/is-checked/) on the wrapper, matching WordPress's
ToggleControl rendering pattern.
The WordPress ToggleControl renders an opacity:0 input[type=checkbox]
that covers the entire .components-form-toggle area (position:absolute,
z-index:1, width/height:100%). Previous approach clicked the wrapper
span, but the click didn't propagate to trigger React's onChange.

Fix: target the actual input element with Playwright's { force: true }
option, which bypasses the visibility check for opacity:0 elements.
This fires the native click event directly on the checkbox, which
React's event delegation picks up to call onChange → handleToggle → PATCH.
@superdav42
Copy link
Copy Markdown
Contributor Author

Pulse diagnostic — toggle PATCH still not firing

Root cause identified from source analysis:

ToggleControl renders:

<span class="components-form-toggle is-checked">  <!-- no click handler -->
  <input class="components-form-toggle__input" type="checkbox" onChange={handler} />
  <span class="components-form-toggle__track"></span>
  <span class="components-form-toggle__thumb"></span>
</span>
<label class="components-toggle-control__label" htmlFor="inspector-toggle-control-N">
  {label}
</label>

Clicking .components-form-toggle (the <span>) does not trigger onChange — it has no click handler and is not a label. The <label> has htmlFor pointing to the input, so clicking the label triggers the checkbox.

Fix options (in order of reliability):

  1. Click the label: card.locator('.components-toggle-control__label').click()
  2. Click the input directly with force: card.locator('input.components-form-toggle__input').click({ force: true })
  3. Dispatch change event on the input: card.locator('input.components-form-toggle__input').dispatchEvent('change')

Option 1 is most semantically correct (mimics real user interaction via label click → input toggle → React onChange).

@superdav42
Copy link
Copy Markdown
Contributor Author

**[Supervisor] CI failure diagnosis — **

Latest CI run still failing. The exact error:

automations.spec.js:668 — expect(patchBody).toMatchObject({ enabled: false })

patchBody is undefined — the PATCH request is never being made. The click is not triggering the API call.

What this means: The element being clicked is not the one that triggers the PATCH. The test intercepts page.route('**/wp-json/gratis-ai-agent/v1/automations/**') and captures the request body. If patchBody is undefined, no PATCH request was made at all.

Suggested next step: Before clicking, add a console.log or use page.waitForRequest with a timeout to confirm whether ANY request is made after the click. Also check the actual automation card component source — search for fetch, apiFetch, or PATCH in the component files to find what event handler triggers the API call and what DOM element it's attached to.

Root cause: WordPress's apiFetch httpV1Middleware converts PATCH/PUT/DELETE
requests to POST with an X-HTTP-Method-Override header. The route handlers
were checking route.request().method() === 'PATCH', but the actual HTTP
method is always POST. The PATCH was never detected, so patchCalled stayed
false.

Fix: resolve the effective method from the X-HTTP-Method-Override header
before checking the method in all route handlers. Also reorder PATCH/DELETE
checks before POST in mockAutomationRoutes to prevent method-override
requests from being caught by the generic POST handler.

This was the actual bug — the checkbox click mechanism (force:true) was
correct all along. The 3 prior attempts focused on the wrong layer.
@superdav42
Copy link
Copy Markdown
Contributor Author

[pulse supervisor] CI failure analysis for latest push:

The 2 failing tests are still using input.components-form-toggle__input (checkbox selector). The test waits for the checkbox to become unchecked after clicking, but it stays checked because the PATCH is never triggered.

Exact failure (WP 6.9):

waiting for locator('.ai-agent-skill-card').filter({ hasText: 'Test Event Automation' }).locator('input.components-form-toggle__input').first()
9 × locator resolved to <input checked type="checkbox" .../>
  - unexpected value "checked"

Required fix: Replace the checkbox selector with the card action button that triggers the PATCH. The checkbox is opacity:0 and does not trigger the PATCH endpoint. Use:

const toggleBtn = card.getByRole('button', { name: /enable|disable/i });
await toggleBtn.click();

Then assert patchCalled is true. Do NOT assert checkbox state — assert the mock was called.

After handleToggle fires the PATCH, the component calls fetchAll() which
GETs the list endpoint (/automations, not /automations/1). The test's
route handler only matched /automations/1, so the list GET fell through
to the beforeEach mock which always returns enabled:true.

Fix: widen the route handler to also intercept the list endpoint after
patchCalled is true, returning the updated automation with enabled:false.
This allows the component to re-render with the disabled state.
@superdav42 superdav42 merged commit 068b16b into main Mar 24, 2026
12 checks passed
superdav42 added a commit that referenced this pull request Mar 25, 2026
…e tests

The CodeRabbit CRITICAL finding from PR #613 flagged that the toggle tests
used checkbox.click({ force: true }) which bypasses the browser's native
event dispatch for opacity:0 elements, preventing React's synthetic onChange
handler from firing correctly.

Replace with checkbox.evaluate( el => el.click() ) which triggers the native
click in the page's JS context, properly firing React's onChange. Update
comments in both the scheduled and event automation toggle tests to explain
the rationale.

The logs route mock fix (Finding 1 — /automations/{id}/logs) was already
addressed in the PR #613 merge commit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

t134: Add E2E tests for automations system (t080/t081)

1 participant