Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ steps:
command: |
buildkite-agent artifact download dist.tar.gz .
tar -xzf dist.tar.gz
echo "--- :docker: Starting wp-env"
make wp-env-start
echo "--- :performing_arts: Running E2E tests"
make test-e2e
plugins: *plugins

Expand Down
84 changes: 2 additions & 82 deletions docs/test-cases.md
Original file line number Diff line number Diff line change
@@ -1,90 +1,10 @@
# Mobile Editor Tests

## Smoke Tests
These test cases require the native undo/redo toolbar and must be verified manually on iOS or Android.

**Purpose:** Verify the editor's core functionality: writing/formatting text, uploading media, saving/publishing, and basic block manipulation.

### S.1. Undo/Redo Actions
## S.1. Undo/Redo Actions

- **Steps:**
- Add, remove, and edit blocks and text.
- Use Undo and Redo buttons.
- **Expected Outcome:** Editor correctly undoes and redoes actions, restoring previous states.

### S.2. Upload an image

- **Steps:**
- Add an Image block.
- Tap "Choose from device" and select an image.
- **Expected Outcome:** Image uploads and displays in the block. An activity indicator is shown while the image is uploading.

### S.3. Upload an video

- **Steps:**
- Add a Video block.
- Tap "Choose from device" and select a video.
- **Expected Outcome:** Video uploads and displays in the block. An activity indicator is shown while the video is uploading.

### S.4. Save and publish a post

- **Steps:**
- Create a new post with text and media.
- Save as draft, then publish.
- **Expected Outcome:** Post is saved and published successfully; content appears as expected.

## Functionality Tests

**Purpose:** Validate deeper content and formatting features, advanced block settings, and robust editor behaviors.

### F.1. Text alignment options

- **Steps:**
- Add a Paragraph or Verse block.
- Type text and use alignment options (left, center, right).
- **Expected Outcome:** Selected alignment is applied to the block content.

### F.2. Add and preview embedded content

- **Steps:**
- Add a Shortcode or Embed block.
- Insert a YouTube or Twitter link.
- Preview the post.
- **Expected Outcome:** Embedded content (e.g., YouTube video) displays correctly in preview.

### F.3. Color and gradient customization

- **Steps:**
- Add a block supporting color (e.g., Buttons, Cover).
- Open color settings, switch between solid and gradient, pick custom colors, and apply.
- **Expected Outcome:** Selected colors/gradients are applied; UI updates accordingly.

### F.4. Gallery block: image uploads and captions

- **Steps:**
- Add a Gallery block, upload multiple images.
- Add captions to gallery and individual images, apply formatting.
- **Expected Outcome:** An activity indicator is shown while the images are uploading. Captions and formatting display as expected.

### F.5. Pattern insertion

- **Steps:**
- Insert a pattern from the inserter.
- **Expected Outcome:** Pattern content appears.

### F.6. Upload an audio file

Known issue: [Audio block unable to upload expected file formats](https://github.com/wordpress-mobile/GutenbergKit/issues/123)

- **Steps:**
- Add an Audio block.
- Tap "Choose from device" and select an audio file.
- **Expected Outcome:** Audio uploads and displays in the block. An activity indicator is shown while the audio is uploading.

### F.7. Upload a file

Known issue: [File block unable to upload expected file formats](https://github.com/wordpress-mobile/GutenbergKit/issues/124)

- **Steps:**
- Add a File block.
- Tap "Choose from device" and select a file.
- **Expected Outcome:** File uploads, filename and download button appear when upload completes.
Binary file added e2e/assets/test-audio.mp3
Binary file not shown.
Binary file added e2e/assets/test-file.pdf
Binary file not shown.
Binary file added e2e/assets/test-image-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/assets/test-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/assets/test-video.mp4
Binary file not shown.
39 changes: 39 additions & 0 deletions e2e/audio-upload.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* External dependencies
*/
import path from 'node:path';
import { test, expect } from '@playwright/test';

/**
* Internal dependencies
*/
import EditorPage from './editor-page';

const TEST_AUDIO = path.resolve( import.meta.dirname, 'assets/test-audio.mp3' );

test.describe( 'Audio Upload', () => {
test( 'should upload an audio file via the Audio block', async ( {
page,
} ) => {
const editor = new EditorPage( page );
await editor.setup();

await editor.insertBlock( 'core/audio' );

// Use the "Upload" button which triggers a file input.
const fileChooserPromise = page.waitForEvent( 'filechooser' );
await page
.getByRole( 'button', { name: 'Upload', exact: true } )
.click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles( TEST_AUDIO );

// Wait for the upload to complete (block gets a numeric media ID).
const attrs = await editor.waitForMediaUpload( 0 );
expect( attrs.id ).toBeGreaterThan( 0 );
expect( attrs.src ).toContain( 'localhost:8888' );

// Verify the audio element is rendered.
await expect( page.locator( '.wp-block-audio audio' ) ).toBeAttached();
} );
} );
104 changes: 104 additions & 0 deletions e2e/color-gradient.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* External dependencies
*/
import { test, expect } from '@playwright/test';

/**
* Internal dependencies
*/
import EditorPage from './editor-page';

test.describe( 'Color and Gradient', () => {
test( 'should apply a background color to a button via settings', async ( {
page,
} ) => {
const editor = new EditorPage( page );
await editor.setup();

await editor.insertBlock( 'core/buttons' );

// Type into the button so the inner button block is focused.
const buttonText = page.getByRole( 'textbox', {
name: 'Button text',
} );
await buttonText.click();
await page.keyboard.type( 'Colored' );

// Open block settings and navigate to the Styles tab.
await editor.openBlockSettings();
await page.getByRole( 'tab', { name: 'Styles' } ).click();

// Click the Background color control.
await page
.locator( '.block-settings-menu' )
.getByRole( 'button', { name: 'Background' } )
.click();

// Pick the first color option in the palette.
await page
.locator( '.components-circular-option-picker__option' )
.first()
.click();

// Close the settings popover before reading attributes.
await page.keyboard.press( 'Escape' );

// Verify the inner button block got a background color.
const blocks = await editor.getBlocks();
const innerButton = blocks[ 0 ].innerBlocks[ 0 ];
expect(
innerButton.attributes.backgroundColor ||
innerButton.attributes.style?.color?.background
).toBeTruthy();
} );

test( 'should have theme gradients available in editor settings', async ( {
page,
} ) => {
const editor = new EditorPage( page );
await editor.setup();

// Verify that theme gradients are loaded from wp-env editor settings.
const hasGradients = await page.evaluate( () => {
const settings = window.wp.data
.select( 'core/block-editor' )
.getSettings();
return (
Array.isArray( settings.gradients ) &&
settings.gradients.length > 0
);
} );
expect( hasGradients ).toBe( true );
} );

test( 'should apply a gradient to a button via data store', async ( {
page,
} ) => {
const editor = new EditorPage( page );
await editor.setup();

await editor.insertBlock( 'core/buttons' );

// Apply a gradient to the inner button block via the data store.
await page.evaluate( () => {
const blocks = window.wp.data
.select( 'core/block-editor' )
.getBlocks();
const innerButton = blocks[ 0 ]?.innerBlocks?.[ 0 ];
if ( innerButton ) {
window.wp.data
.dispatch( 'core/block-editor' )
.updateBlockAttributes( innerButton.clientId, {
gradient: 'vivid-cyan-blue-to-vivid-purple',
} );
}
} );

// Verify the gradient was applied.
const blocks = await editor.getBlocks();
const innerButton = blocks[ 0 ].innerBlocks[ 0 ];
expect( innerButton.attributes.gradient ).toBe(
'vivid-cyan-blue-to-vivid-purple'
);
} );
} );
4 changes: 3 additions & 1 deletion e2e/editor-error.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ test.describe( 'Editor Error Handling', () => {
test( 'should show plugin load failure notice and keep editor functional', async ( {
page,
} ) => {
// Enable plugins without providing API endpoints. This causes
// Enable plugins with an unreachable API root. This causes
// fetchEditorAssets to fail, resulting in the plugin load notice.
const editor = new EditorPage( page );
await editor.setup( {
siteApiRoot: 'http://localhost:1/',
authHeader: '',
post: {
id: 1,
type: 'post',
Expand Down
106 changes: 99 additions & 7 deletions e2e/editor-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,25 @@
*/

/**
* Default GBKit configuration for dev-mode testing.
* Internal dependencies
*/
import { credentials, getEditorSettings } from './wp-env-fixtures';

/**
* Default GBKit configuration for wp-env testing.
*
* @type {Object}
*/
const DEFAULT_GBKIT = {
siteApiRoot: credentials.siteApiRoot,
authHeader: credentials.authHeader,
siteApiNamespace: [],
namespaceExcludedPaths: [],
post: {
id: -1,
id: 1,
type: 'post',
restBase: 'posts',
restNamespace: 'wp/v2',
status: 'draft',
title: '',
content: '',
Expand All @@ -34,12 +45,24 @@ export default class EditorPage {
/**
* Navigate to the editor and wait for it to be fully ready.
*
* @param {Object} [gbkit] Optional GBKit config override.
* @param {Object} [gbkit] Optional GBKit config override (merged with defaults).
*/
async setup( gbkit = DEFAULT_GBKIT ) {
await this.#page.addInitScript( ( config ) => {
window.GBKit = config;
}, gbkit );
async setup( gbkit = {} ) {
const editorSettings = await getEditorSettings();

const config = {
...DEFAULT_GBKIT,
editorSettings,
...gbkit,
post: {
...DEFAULT_GBKIT.post,
...gbkit.post,
},
};

await this.#page.addInitScript( ( cfg ) => {
window.GBKit = cfg;
}, config );

await this.#page.goto( '/?dev_mode=1' );

Expand Down Expand Up @@ -165,4 +188,73 @@ export default class EditorPage {
window.wp.data.select( 'core/block-editor' ).getBlocks()
);
}

/**
* Read a single attribute from a root-level block.
*
* @param {number} index Zero-based block index.
* @param {string} attribute Attribute name.
* @return {Promise<*>} The attribute value.
*/
async getBlockAttribute( index, attribute ) {
return await this.#page.evaluate(
( { idx, attr } ) => {
const blocks = window.wp.data
.select( 'core/block-editor' )
.getBlocks();
return blocks[ idx ]?.attributes?.[ attr ];
},
{ idx: index, attr: attribute }
);
}

/**
* Open the "Align text" dropdown and select an alignment option.
*
* @param {string} alignment Alignment label (e.g. 'Align text center').
*/
async setTextAlignment( alignment ) {
await this.#page.getByRole( 'button', { name: 'Align text' } ).click();
await this.#page
.getByRole( 'menuitemradio', { name: alignment } )
.click();
}

/**
* Open the block settings popover (the "Block Settings" cog in the toolbar).
*
* The popover is only available when a block is selected.
*/
async openBlockSettings() {
await this.#page
.getByRole( 'button', { name: 'Block Settings' } )
.click();
}

/**
* Wait for a media upload to complete on a block at the given index.
*
* Polls the block's `id` attribute until it becomes a positive number,
* indicating the upload has finished and the media was assigned a WP ID.
*
* @param {number} index Zero-based block index.
* @param {number} timeout Max wait time in milliseconds.
* @return {Promise<Object>} The block's attributes after upload completes.
*/
async waitForMediaUpload( index, timeout = 30_000 ) {
await this.#page.waitForFunction(
( { idx } ) => {
const blocks = window.wp.data
.select( 'core/block-editor' )
.getBlocks();
const block = blocks[ idx ];
return block?.attributes?.id > 0;
},
{ idx: index },
{ timeout }
);

const blocks = await this.getBlocks();
return blocks[ index ].attributes;
}
}
Loading
Loading