Skip to content

fix: resolve iOS E2E test failures in block inserter#338

Merged
dcalhoun merged 2 commits intotrunkfrom
fix/ios-e2e-duplicate-block-buttons
Feb 27, 2026
Merged

fix: resolve iOS E2E test failures in block inserter#338
dcalhoun merged 2 commits intotrunkfrom
fix/ios-e2e-duplicate-block-buttons

Conversation

@dcalhoun
Copy link
Member

@dcalhoun dcalhoun commented Feb 25, 2026

What?

Fix iOS E2E test failures (testUndoRedoAfterTyping, testInsertImageBlock) when interacting with the block inserter.

Why?

Two separate issues caused the failures:

  1. CI: stale JS bundle — The test-ios-e2e Makefile target only copied dist/ into the iOS bundle directory (ios/Sources/GutenbergKit/Gutenberg/) when that directory didn't exist. Since it's checked into git, it always exists on CI checkout, so the freshly built dist/ from the build-react pipeline step was never copied over the stale git-tracked bundle. This caused blocks to not appear in the inserter at all on CI.

  2. Duplicate button matches — Blocks like "Paragraph" intentionally appear in multiple inserter sections (both gbk-most-used and their category section). When the test helper queries app.buttons["Paragraph"], XCTest finds both instances and fails with "Multiple matching elements found".

How?

  • Makefile: Always replace the iOS bundle directory with the current dist/ contents before running E2E tests, instead of conditionally skipping the copy when the directory already exists.
  • Test helper: Use .firstMatch on the button query in EditorUITestHelpers.insertBlock, since both matching buttons insert the same block.

Testing Instructions

  1. Run make test-ios-e2e
  2. Verify all E2E tests pass

Accessibility Testing Instructions

No accessibility-facing UI changes.

Blocks like Paragraph appear in multiple inserter sections (most-used
and their category section), causing `app.buttons["Paragraph"]` to
match multiple elements and fail with "Multiple matching elements found".

Use `.firstMatch` since both buttons perform the same insert action.
@dcalhoun dcalhoun added the [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. label Feb 25, 2026
The test-ios-e2e target only copied dist/ into the iOS bundle directory
when ios/Sources/GutenbergKit/Gutenberg/ didn't exist. Since that
directory is checked into git, it always exists on CI checkout, so the
freshly built dist/ from the build-react pipeline step was never copied
over the stale git-tracked bundle.

Always replace the iOS bundle with the current dist/ contents to ensure
E2E tests run against the latest JS build.
@dcalhoun dcalhoun changed the title fix: use firstMatch in block inserter E2E test helper fix: resolve iOS E2E test failures in block inserter Feb 25, 2026
@dcalhoun dcalhoun marked this pull request as ready for review February 25, 2026 19:02
@dcalhoun dcalhoun enabled auto-merge (squash) February 25, 2026 19:02
@dcalhoun dcalhoun requested a review from kean February 25, 2026 19:02
dcalhoun added a commit that referenced this pull request Feb 26, 2026
The Android assets directory is checked into git, so the conditional
copy (only when the directory doesn't exist) would always skip,
leaving stale build output. Always clean and replace with the
current dist/ contents, matching the fix applied to iOS in PR #338.
dcalhoun added a commit that referenced this pull request Feb 27, 2026
* test: Add Espresso Web and Compose UI Test dependencies for Android E2E

Add espresso-web to the version catalog and wire up the test
dependencies needed by the instrumented E2E suite: espresso-web for
WebView DOM interaction, compose-ui-test-junit4 for Compose semantics
assertions, and ui-test-manifest for the debug variant. Disable
animations in testOptions to avoid flakiness.

* test: Add Android E2E tests for editor loading and undo/redo

Add EditorTestHelpers (mirrors iOS EditorUITestHelpers) and
EditorInteractionTest (mirrors iOS EditorInteractionUITest) with two
test cases:

- testEditorWebViewBecomesVisible: navigates Main → SitePreparation →
  Editor and verifies the Gutenberg WebView loads.
- testUndoRedoAfterTyping: full native-to-web bridge round-trip —
  types in title, inserts a Paragraph block via the web inserter,
  types in content, then verifies Undo/Redo toolbar button state
  propagates correctly through the bridge.

Key implementation details:
- Uses document.execCommand('insertText') instead of Espresso Web's
  webKeys(), which fails on Gutenberg's contenteditable rich text.
- Clicks WebView toolbar buttons via JS pointer/mouse event dispatch
  for React synthetic event compatibility.
- Inserts blocks through the web block inserter (Add block → Paragraph)
  since Android has no native block inserter.

Removes the auto-generated ExampleInstrumentedTest placeholder.

* test: Add Makefile targets for Android E2E tests

Add test-android-e2e (production build) and test-android-e2e-dev
(Vite dev server) targets, mirroring the existing test-ios-e2e /
test-ios-e2e-dev pattern.

Includes an ENSURE_ANDROID_DEVICE macro that checks for any
connected device via adb and auto-boots the first available AVD
if none is found.

* fix: Always replace Android assets before E2E tests

The Android assets directory is checked into git, so the conditional
copy (only when the directory doesn't exist) would always skip,
leaving stale build output. Always clean and replace with the
current dist/ contents, matching the fix applied to iOS in PR #338.

* fix: Restore emulator animations after E2E tests

Replace `testOptions { animationsDisabled = true }` with a custom
DisableAnimationsRule that saves the original animation scale values,
disables them for the test, and restores them in a finally block.

The Gradle setting permanently disabled animations on the emulator
with no cleanup. The rule-based approach ensures animations are
always restored, even if tests fail.

* fix: Replace class-based selectors with aria attributes in Android E2E tests

Use semantic selectors (aria-label, aria-modal, role, placeholder) instead
of CSS class names so tests are resilient to styling refactors.

- Scope ADD_BLOCK_SELECTOR to [aria-label='Editor toolbar'] to
  disambiguate the toolbar inserter from the inline block appender
- Replace .block-editor-inserter__popover with [role='dialog'][aria-modal]
  and match block options by textContent within [role='option'] elements
- Replace textarea.editor-post-text-editor with placeholder attribute

* fix: Replace Kotlin assert() with JUnit Assert methods in Android E2E tests

Kotlin's assert() compiles to Java assert statements, which can be
silently disabled at the JVM level. On Android, assertions are disabled
by default unless explicitly enabled with -ea. Using JUnit's
Assert.assertEquals, Assert.assertTrue, and Assert.assertFalse ensures
assertions always execute during test runs.

* fix: Quote $ANDROID_HOME paths in ENSURE_ANDROID_DEVICE macro

Add quotes around $ANDROID_HOME/emulator/emulator paths so the macro
handles SDK installations in directories containing spaces.

* refactor: Move Compose test dependencies to version catalog

Move the hardcoded androidx.compose.ui:ui-test-junit4 and
ui-test-manifest dependency strings in build.gradle.kts to named
entries in libs.versions.toml for consistency with the other Compose
library declarations.

* refactor: Extract waitUntil/runCatching pattern into extension functions

Add waitUntilAsserts and waitForNodeWithText extension functions on
AndroidComposeTestRule to replace the repeated waitUntil { runCatching
{ ... }.getOrDefault(false) } pattern that appeared in five places.

* refactor: Simplify clickViaJs to use el.click() instead of full event sequence

The previous implementation dispatched five synthetic events
(pointerdown, mousedown, pointerup, mouseup, click) to ensure React
picked up the interaction. In practice, el.click() dispatches a real
click event that bubbles through the DOM, which is sufficient for
React 18's event delegation. This simplifies the JS and removes the
unnecessary PointerEvent/MouseEvent ceremony.

* refactor: Consolidate WebView element waiting into single JS-based approach

Replace the dual waiting mechanism (Espresso Web findElement + JS
querySelector) with a single JS-based approach. The Espresso Web
variant used findElement with a getText/webMatches assertion, which
can fail on elements that don't pass Espresso's built-in visibility
check. The JS approach using document.querySelector is simpler and
handles both visible and non-visible elements consistently.

This removes the webMatches, getText, and notNullValue imports that
were only needed by the Espresso-based variant.

* refactor: Use Espresso Web XPath locator for block insertion

Replace the hand-rolled JS that iterates over role="option" elements
to find and click a block by name with Espresso Web's findElement using
an XPath locator. This is more idiomatic and leverages the framework's
built-in element location instead of custom JS string matching.

The dialog wait still uses the JS-based waitForWebViewElement to ensure
the inserter is rendered before attempting the XPath lookup.

* refactor: Extract inline inserter dialog selectors into named values

The insertBlock method had two inline selectors — a CSS selector for
the inserter dialog and an XPath for matching block options by name.
Extract the CSS selector to INSERTER_DIALOG_SELECTOR (alongside the
other named constants) and the XPath template to an
inserterOptionXpath helper function for clarity.

* refactor: Extract runJs() helper to deduplicate WebView JS execution

Centralizes the repeated `onWebView().forceJavascriptEnabled()
.perform(script(js)).get()` boilerplate into a single `runJs()`
method, used by clickViaJs, typeViaExecCommand, readTextViaJs,
and waitForConditionViaJs.

* fix: Narrow catch (Throwable) to catch (Exception) in waitForConditionViaJs

Avoids silently swallowing JVM errors like OutOfMemoryError or
StackOverflowError during WebView polling.

* refactor: Use Kotlin multiline strings for JS snippets

Replace string concatenation with trimIndent() multiline strings
in clickViaJs, typeViaExecCommand, readTextViaJs, and
waitForWebViewElement for improved readability.

* fix: Add boot timeout to ENSURE_ANDROID_DEVICE macro

Prevents the emulator boot loop from hanging indefinitely by
failing after 120 seconds (60 iterations x 2s sleep).

* fix: Default to 1.0 for missing animation settings in DisableAnimationsRule

When a device has no value for an animation scale setting, adb
returns the string "null". Restoring "null" as the value could
leave animations in an unexpected state. Default to "1.0" instead.

* fix: Check execCommand return value in typeViaExecCommand

Verify that document.execCommand('insertText') returns true and
throw an AssertionError if it fails, catching silent text insertion
failures.

* refactor: Simplify readTextViaJs to only handle textarea elements

All callers target Code Editor textarea fields, so the
contenteditable branch (innerText/textContent) is unused.
Simplify to just return el.value.

* fix: Kill emulator process on boot timeout in ENSURE_ANDROID_DEVICE

Store the background emulator PID and kill it if the boot times out,
preventing an orphaned emulator process from lingering after failure.

* refactor: Add EditorTestRule type alias for verbose compose rule type

Introduces a top-level type alias to reduce repetition of the
AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>
type throughout EditorTestHelpers.

* refactor: Replace JS workarounds with standard Espresso Web APIs

Remove clickViaJs and JS-based waitForWebViewElement in favor of
standard Espresso Web findElement/webClick calls. Testing confirmed
that Espresso Web's visibility checks pass for Gutenberg toolbar
elements, making the JS bypasses unnecessary.

The execCommand('insertText') workaround for text input remains, as
Espresso Web's webKeys() genuinely fails on Gutenberg's contenteditable
with "Cannot set the selection end".
@dcalhoun dcalhoun merged commit 30bb349 into trunk Feb 27, 2026
14 checks passed
@dcalhoun dcalhoun deleted the fix/ios-e2e-duplicate-block-buttons branch February 27, 2026 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants