Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6df7652
test: Add iOS UI test target and editor load tests (XCUITest)
dcalhoun Feb 15, 2026
adac649
test: Add iOS UI editor interaction and bridge tests
dcalhoun Feb 15, 2026
746cec4
test: Add Makefile target and docs for iOS E2E tests
dcalhoun Feb 15, 2026
6af8fe3
fix: Add editor navigation and accessibility labels to iOS E2E tests
dcalhoun Feb 15, 2026
81f446f
chore: Add shared Xcode scheme for GutenbergUITests
dcalhoun Feb 15, 2026
d621a63
test: Remove superficial iOS UI tests
dcalhoun Feb 15, 2026
2c16a4f
test: Add undo/redo after typing iOS UI test
dcalhoun Feb 15, 2026
304fa47
test: Add code editor toggle and block inserter iOS UI tests
dcalhoun Feb 15, 2026
6ef4cad
test: Consolidate iOS UI tests into single file
dcalhoun Feb 15, 2026
d04b495
refactor: Split typeInContent into insertBlock and typeInContent helpers
dcalhoun Feb 15, 2026
26eafd0
perf: Disable parallel simulator testing for iOS E2E tests
dcalhoun Feb 15, 2026
3a69fd8
refactor: Extract iOS E2E test helpers into EditorUITestHelpers
dcalhoun Feb 15, 2026
7d08713
test: Add editor content verification to iOS E2E tests
dcalhoun Feb 15, 2026
7a306fb
refactor: Read title and content in a single mode toggle
dcalhoun Feb 15, 2026
19d016e
test: Remove redundant code editor toggle test
dcalhoun Feb 15, 2026
34496f9
test: Verify content after undo in undo/redo test
dcalhoun Feb 15, 2026
3f9497e
feat: Add dev server support for iOS E2E tests
dcalhoun Feb 24, 2026
02c0692
docs: Add Web E2E subsection and clarify local vs CI modes
dcalhoun Feb 24, 2026
20d4626
ci: Add Buildkite step for iOS E2E tests
dcalhoun Feb 24, 2026
6a7197f
refactor: Remove unnecessary npm-dependencies from test-ios-e2e-dev
dcalhoun Feb 24, 2026
f070c76
ci: Rename Buildkite E2E step to Test Web E2E
dcalhoun Feb 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ steps:
command: make test-js
plugins: *plugins

- label: ':performing_arts: Test E2E'
- label: ':performing_arts: Test Web E2E'
depends_on: build-react
command: |
buildkite-agent artifact download dist.tar.gz .
Expand Down Expand Up @@ -53,3 +53,11 @@ steps:
- label: ':swift: Test Swift Package'
command: swift test
plugins: *plugins

- label: ':ios: Test iOS E2E'
depends_on: build-react
command: |
buildkite-agent artifact download dist.tar.gz .
tar -xzf dist.tar.gz
make test-ios-e2e
plugins: *plugins
37 changes: 37 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,43 @@ test-js-watch: npm-dependencies ## Run JavaScript tests in watch mode
test-swift-package: build ## Run Swift package tests
$(call XCODEBUILD_CMD, test)

.PHONY: test-ios-e2e
test-ios-e2e: ## Run iOS E2E tests against the production build
@if [ ! -d "dist" ]; then \
$(MAKE) build; \
else \
echo "--- :white_check_mark: Using existing build. Use 'make build REFRESH_JS_BUILD=1' to rebuild."; \
fi
@if [ ! -d "./ios/Sources/GutenbergKit/Gutenberg" ]; then \
echo "--- :open_file_folder: Copying build into iOS bundle"; \
cp -r ./dist/. ./ios/Sources/GutenbergKit/Gutenberg/; \
fi
@echo "--- :ios: Running iOS E2E Tests (production build)"
@set -o pipefail && \
xcodebuild test \
-project ./ios/Demo-iOS/Gutenberg.xcodeproj \
-scheme GutenbergUITests \
-sdk iphonesimulator \
-destination '${SIMULATOR_DESTINATION}' \
| xcbeautify

.PHONY: test-ios-e2e-dev
test-ios-e2e-dev: ## Run iOS E2E tests against the Vite dev server (must be running)
@if ! curl -sf http://localhost:5173 > /dev/null 2>&1; then \
echo "Error: Dev server is not running at http://localhost:5173"; \
echo "Start it first with: make dev-server"; \
exit 1; \
fi
@echo "--- :ios: Running iOS E2E Tests (dev server)"
@set -o pipefail && \
TEST_RUNNER_GUTENBERG_EDITOR_URL=http://localhost:5173 \
xcodebuild test \
-project ./ios/Demo-iOS/Gutenberg.xcodeproj \
-scheme GutenbergUITests \
-sdk iphonesimulator \
-destination '${SIMULATOR_DESTINATION}' \
| xcbeautify

.PHONY: test-android
test-android: ## Run Android tests
@echo "--- :android: Running Android Tests"
Expand Down
59 changes: 58 additions & 1 deletion docs/code/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ make test-android

## E2E Tests

E2E tests use Playwright to load the editor in a headless Chromium browser and verify Gutenberg editor logic — block operations, text formatting, split/merge, and data store state. They run against the Vite dev server with no native layer involved.
### Web E2E Tests (Playwright)

These tests use Playwright to load the editor in a headless Chromium browser and verify Gutenberg editor logic — block operations, text formatting, split/merge, and data store state. No native layer is involved.

Test files live in `e2e/*.spec.js`.

Locally, Playwright starts the Vite dev server (`npm run dev` on `:5173`) and reuses an existing one if already running. On CI, it uses a production preview build (`npm run preview` on `:4173`) with serial workers and retries. This is configured in `playwright.config.js`.

Run tests:

```bash
Expand All @@ -46,6 +50,59 @@ Run in interactive UI mode:
make test-e2e-ui
```

### iOS E2E Tests (XCUITest)

- Framework: XCUITest
- Test files: `ios/Demo-iOS/GutenbergUITests/`
- Requires: Xcode and an iOS Simulator

These tests launch the Demo iOS app via `XCUIApplication` and verify native shell behavior — toolbar rendering, menu interactions, WebView lifecycle, and native-to-JS bridge state synchronization.

There are two ways to run the tests, depending on how the editor JS is served.

#### Dev server (local development)

Uses the Vite dev server for faster iteration — no production build required. Start the dev server in one terminal, then run the tests in another:

```bash
# Terminal 1
make dev-server

# Terminal 2
make test-ios-e2e-dev
```

This sets `TEST_RUNNER_GUTENBERG_EDITOR_URL`, which `xcodebuild` forwards to the test runner process (with the `TEST_RUNNER_` prefix stripped). The test setup then passes `GUTENBERG_EDITOR_URL` to the app under test via `launchEnvironment`, so the WebView loads from `http://localhost:5173` instead of the bundled assets.

#### Production build (CI)

Uses the production JS bundle built by Vite. This is what CI runs and is the default `make test-ios-e2e` target:

```bash
make test-ios-e2e
```

The target depends on `build` and will handle it automatically.

#### Switching between modes

The mode is controlled by the `GUTENBERG_EDITOR_URL` environment variable. When set, `EditorViewController` loads from that URL; otherwise it loads from the bundled `index.html`.

- `make test-ios-e2e-dev` — sets `TEST_RUNNER_GUTENBERG_EDITOR_URL=http://localhost:5173` and checks that the dev server is running before starting.
- `make test-ios-e2e` — does not set the variable; runs a production build first.

You can also pass the variable directly if you need a custom URL:

```bash
TEST_RUNNER_GUTENBERG_EDITOR_URL=http://localhost:5173 xcodebuild test \
-project ./ios/Demo-iOS/Gutenberg.xcodeproj \
-scheme GutenbergUITests \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 17'
```

> **Note:** The `TEST_RUNNER_` prefix is an `xcodebuild` convention — variables with this prefix are forwarded to test runner processes with the prefix removed.

## Code Quality

Before submitting a pull request, ensure your code passes formatting and linting checks.
Expand Down
111 changes: 111 additions & 0 deletions ios/Demo-iOS/Gutenberg.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@
2468526C2EAACCA100ED1F09 /* ConfigurationStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246852692EAACCA100ED1F09 /* ConfigurationStorage.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
AA0000012F00000000000007 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0C4F59832BEFF4970028BD96 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0C4F598A2BEFF4970028BD96;
remoteInfo = Gutenberg;
};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
0C4F598B2BEFF4970028BD96 /* Gutenberg.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gutenberg.app; sourceTree = BUILT_PRODUCTS_DIR; };
0C4F59A72BEFF4980028BD96 /* ConfigurationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItem.swift; sourceTree = "<group>"; };
Expand All @@ -26,10 +36,12 @@
0CE8E7922C339B1B00B9DC67 /* GutenbergKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = GutenbergKit; path = ../..; sourceTree = "<group>"; };
246852682EAACCA100ED1F09 /* AuthenticationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationManager.swift; sourceTree = "<group>"; };
246852692EAACCA100ED1F09 /* ConfigurationStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationStorage.swift; sourceTree = "<group>"; };
AA0000012F00000000000001 /* GutenbergUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GutenbergUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
2468525B2EAAC62B00ED1F09 /* Views */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Views; sourceTree = "<group>"; };
AA0000012F0000000000000B /* GutenbergUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = GutenbergUITests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -42,6 +54,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
AA0000012F00000000000004 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
Expand All @@ -52,6 +71,7 @@
0CE8E7882C339B0600B9DC67 /* Sources */,
0C83424D2C339B7F00CAA762 /* Resources */,
0CE8E78A2C339B0600B9DC67 /* PreviewContent */,
AA0000012F0000000000000B /* GutenbergUITests */,
0C4F598C2BEFF4970028BD96 /* Products */,
0CF6E04A2BEFF60E00EDEE8A /* Frameworks */,
);
Expand All @@ -61,6 +81,7 @@
isa = PBXGroup;
children = (
0C4F598B2BEFF4970028BD96 /* Gutenberg.app */,
AA0000012F00000000000001 /* GutenbergUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
Expand Down Expand Up @@ -136,6 +157,27 @@
productReference = 0C4F598B2BEFF4970028BD96 /* Gutenberg.app */;
productType = "com.apple.product-type.application";
};
AA0000012F00000000000002 /* GutenbergUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = AA0000012F00000000000008 /* Build configuration list for PBXNativeTarget "GutenbergUITests" */;
buildPhases = (
AA0000012F00000000000003 /* Sources */,
AA0000012F00000000000004 /* Frameworks */,
AA0000012F00000000000005 /* Resources */,
);
buildRules = (
);
dependencies = (
AA0000012F00000000000006 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
AA0000012F0000000000000B /* GutenbergUITests */,
);
name = GutenbergUITests;
productName = GutenbergUITests;
productReference = AA0000012F00000000000001 /* GutenbergUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
Expand All @@ -149,6 +191,10 @@
0C4F598A2BEFF4970028BD96 = {
CreatedOnToolsVersion = 15.1;
};
AA0000012F00000000000002 = {
CreatedOnToolsVersion = 16.2;
TestTargetID = 0C4F598A2BEFF4970028BD96;
};
};
};
buildConfigurationList = 0C4F59862BEFF4970028BD96 /* Build configuration list for PBXProject "Gutenberg" */;
Expand All @@ -168,6 +214,7 @@
projectRoot = "";
targets = (
0C4F598A2BEFF4970028BD96 /* Gutenberg */,
AA0000012F00000000000002 /* GutenbergUITests */,
);
};
/* End PBXProject section */
Expand All @@ -182,6 +229,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
AA0000012F00000000000005 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand All @@ -196,13 +250,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
AA0000012F00000000000003 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
245D6BE42EDFCD640076D741 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 245D6BE32EDFCD640076D741 /* GutenbergKit */;
};
AA0000012F00000000000006 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 0C4F598A2BEFF4970028BD96 /* Gutenberg */;
targetProxy = AA0000012F00000000000007 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
Expand Down Expand Up @@ -395,6 +461,42 @@
};
name = Release;
};
AA0000012F00000000000009 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.gutenberg.uitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Gutenberg;
};
name = Debug;
};
AA0000012F0000000000000A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.wordpress.gutenberg.uitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = Gutenberg;
};
name = Release;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
Expand All @@ -416,6 +518,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
AA0000012F00000000000008 /* Build configuration list for PBXNativeTarget "GutenbergUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AA0000012F00000000000009 /* Debug */,
AA0000012F0000000000000A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
Expand Down
Loading
Loading