Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluate WP commands with shell-like API in e2e tests #33720

Closed
wants to merge 6 commits into from
Closed
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
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions packages/e2e-test-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,18 @@ _Returns_

- `Promise`: Promise resolving when publish is complete.

### rest

Call REST API using `apiFetch` to build and clear test states.

_Parameters_

- _options_ `Object`: `apiFetch` options.

_Returns_

- `Promise<any>`: The response value.

### saveDraft

Saves the post as a draft, resolving once the request is complete (once the
Expand Down Expand Up @@ -648,6 +660,27 @@ _Parameters_

- _mocks_ `Array`: Array of mock settings.

### shell

Evaluate WordPress commands remotely via wp-shell test plugin.

_Usage_

```js
await shell`
return switch_theme( 'twentytwenty' );
`;
```

_Parameters_

- _strings_ `Array<string>|string`: The commands strings.
- _args_ `...any`: Additional arguments.

_Returns_

- `Promise<any>`: Evaluated result.

### showBlockToolbar

The block toolbar is not always visible while typing.
Expand Down
1 change: 1 addition & 0 deletions packages/e2e-test-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"module": "build-module/index.js",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/keycodes": "file:../keycodes",
"@wordpress/url": "file:../url",
"lodash": "^4.17.21",
Expand Down
27 changes: 12 additions & 15 deletions packages/e2e-test-utils/src/activate-plugin.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
/**
* Internal dependencies
*/
import { switchUserToAdmin } from './switch-user-to-admin';
import { switchUserToTest } from './switch-user-to-test';
import { visitAdminPage } from './visit-admin-page';
import { shell } from './wp-shell';

/**
* Activates an installed plugin.
*
* @param {string} slug Plugin slug.
*/
export async function activatePlugin( slug ) {
await switchUserToAdmin();
await visitAdminPage( 'plugins.php' );
const disableLink = await page.$(
`tr[data-slug="${ slug }"] .deactivate a`
);
if ( disableLink ) {
await switchUserToTest();
return;
}
await page.click( `tr[data-slug="${ slug }"] .activate a` );
await page.waitForSelector( `tr[data-slug="${ slug }"] .deactivate a` );
await switchUserToTest();
await shell`
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );

$plugins = get_plugins();

foreach ( $plugins as $plugin_id => $plugin ) {
if ( '${ slug }' == sanitize_title( $plugin['Title'] ) ) {
return activate_plugin( $plugin_id );
}
}
`;
}
21 changes: 4 additions & 17 deletions packages/e2e-test-utils/src/activate-theme.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
/**
* Internal dependencies
*/
import { switchUserToAdmin } from './switch-user-to-admin';
import { switchUserToTest } from './switch-user-to-test';
import { visitAdminPage } from './visit-admin-page';
import { shell } from './wp-shell';

/**
* Activates an installed theme.
*
* @param {string} slug Theme slug.
*/
export async function activateTheme( slug ) {
await switchUserToAdmin();
await visitAdminPage( 'themes.php' );

const activateButton = await page.$(
`div[data-slug="${ slug }"] .button.activate`
);
if ( ! activateButton ) {
switchUserToTest();
return;
}

await page.click( `div[data-slug="${ slug }"] .button.activate` );
await page.waitForSelector( `div[data-slug="${ slug }"].active` );
await switchUserToTest();
await shell`
switch_theme( '${ slug }' );
`;
}
25 changes: 12 additions & 13 deletions packages/e2e-test-utils/src/deactivate-plugin.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
/**
* Internal dependencies
*/
import { switchUserToAdmin } from './switch-user-to-admin';
import { switchUserToTest } from './switch-user-to-test';
import { visitAdminPage } from './visit-admin-page';
import { shell } from './wp-shell';

/**
* Deactivates an active plugin.
*
* @param {string} slug Plugin slug.
*/
export async function deactivatePlugin( slug ) {
await switchUserToAdmin();
await visitAdminPage( 'plugins.php' );
const deleteLink = await page.$( `tr[data-slug="${ slug }"] .delete a` );
if ( deleteLink ) {
await switchUserToTest();
return;
}
await page.click( `tr[data-slug="${ slug }"] .deactivate a` );
await page.waitForSelector( `tr[data-slug="${ slug }"] .delete a` );
await switchUserToTest();
await shell`
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );

$plugins = get_plugins();

foreach ( $plugins as $plugin_id => $plugin ) {
if ( '${ slug }' == sanitize_title( $plugin['Title'] ) ) {
return deactivate_plugins( $plugin_id );
}
}
`;
}
2 changes: 2 additions & 0 deletions packages/e2e-test-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,7 @@ export { showBlockToolbar } from './show-block-toolbar';
export { openPreviewPage } from './preview';
export { wpDataSelect } from './wp-data-select';
export { deleteAllWidgets } from './widgets';
export { rest } from './rest-api';
export { shell } from './wp-shell';

export * from './mocks';
57 changes: 57 additions & 0 deletions packages/e2e-test-utils/src/rest-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* External dependencies
*/
import fetch from 'node-fetch';

/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';

/**
* Internal dependencies
*/
import { WP_BASE_URL, WP_USERNAME, WP_PASSWORD } from './shared/config';

// `apiFetch` expects `window.fetch` to be available in its default handler.
global.window = global.window || {};
global.window.fetch = fetch;

const setAPIRootURL = ( async () => {
// Discover the API root url using link header.
// See https://developer.wordpress.org/rest-api/using-the-rest-api/discovery/#link-header
const res = await fetch( WP_BASE_URL, { method: 'HEAD' } );
const links = res.headers.get( 'link' );
const restLink = links.match( /<([^>]+)>; rel="https:\/\/api\.w\.org\/"/ );

if ( ! restLink ) {
throw new Error( `Failed to discover REST API endpoint.
Link header: ${ links }` );
}

const rootURL = restLink[ 1 ];
apiFetch.use( apiFetch.createRootURLMiddleware( rootURL ) );
} )();

/**
* Call REST API using `apiFetch` to build and clear test states.
*
* @param {Object} options `apiFetch` options.
* @return {Promise<any>} The response value.
*/
async function rest( options = {} ) {
await setAPIRootURL;

return await apiFetch( {
...options,
headers: {
...( options.headers || {} ),
// Authorize via basic authentication and test plugin.
Authorization: `Basic ${ Buffer.from(
`${ WP_USERNAME }:${ WP_PASSWORD }`
).toString( 'base64' ) }`,
},
} );
}

export { rest };
46 changes: 25 additions & 21 deletions packages/e2e-test-utils/src/widgets.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
/**
* Internal dependencies
*/
import { activatePlugin } from './activate-plugin';
import { deactivatePlugin } from './deactivate-plugin';
import { visitAdminPage } from './visit-admin-page';
import { rest } from './rest-api';

/**
* Delete all the widgets in the widgets screen.
*/
export async function deleteAllWidgets() {
// TODO: Deleting widgets in the new widgets screen is cumbersome and slow.
// To workaround this for now, we visit the old widgets screen to delete them.
await activatePlugin( 'gutenberg-test-classic-widgets' );
const [ widgets, sidebars ] = await Promise.all( [
rest( { path: '/wp/v2/widgets' } ),
rest( { path: '/wp/v2/sidebars' } ),
] );

await visitAdminPage( 'widgets.php' );
await rest( {
method: 'POST',
path: '/batch/v1',
data: {
requests: widgets.map( ( widget ) => ( {
method: 'DELETE',
path: `/wp/v2/widgets/${ widget.id }?force=true`,
} ) ),
validation: 'require-all-validate',
},
} );

let widget = await page.$( '.widgets-sortables .widget' );

// We have to do this one-by-one since there might be race condition when deleting multiple widgets at once.
while ( widget ) {
const deleteButton = await widget.$( 'button.widget-control-remove' );
const id = await widget.evaluate( ( node ) => node.id );
await deleteButton.evaluate( ( node ) => node.click() );
// Wait for the widget to be removed from DOM.
await page.waitForSelector( `#${ id }`, { hidden: true } );

widget = await page.$( '.widgets-sortables .widget' );
}

await deactivatePlugin( 'gutenberg-test-classic-widgets' );
await Promise.all(
sidebars.map( ( sidebar ) =>
rest( {
method: 'POST',
path: `/wp/v2/sidebars/${ sidebar.id }`,
data: { id: sidebar.id, widgets: [] },
} )
)
);
}
39 changes: 39 additions & 0 deletions packages/e2e-test-utils/src/wp-shell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Internal dependencies
*/
import { rest } from './rest-api';

/**
* Evaluate WordPress commands remotely via wp-shell test plugin.
*
* @example
*
* ```js
* await shell`
* return switch_theme( 'twentytwenty' );
* `;
* ```
*
* @param {Array<string>|string} strings The commands strings.
* @param {...any} args Additional arguments.
* @return {Promise<any>} Evaluated result.
*/
async function shell( strings, ...args ) {
if ( Array.isArray( strings ) ) {
let string = strings[ 0 ];
for ( let i = 0; i < args.length; i += 1 ) {
string += args[ i ] + strings[ i + 1 ];
}
return shell( string );
}

const response = await rest( {
method: 'POST',
path: '/wp-shell/eval',
data: { commands: strings },
} );

return response.result;
}

export { shell };
Loading