Skip to content
Merged
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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,27 @@ Reads a file and returns its contents as a string.

Global setup function for Playwright tests. Ensures fixtures are deleted and global context is set up properly.

#### 👤 User Utilities

##### `createTranslator( langSlugs, userName = '' )`

Creates a translator user, based on the `editor` role.

- **Parameters:**
- `langSlugs`: List of language slugs.
- `userName`: Optional. A user name. Defaults to `XX-YY-translator`, where `XX` and `YY` are language slugs.
- **Returns:** Promise resolving to a user object containing ID, user name, and password.

##### `switchToUser( user, admin, requestUtils )`

Switches to the given user.

- **Parameters:**
- `user`: The user to switch to (an object containing a user name and a password).
- `admin`: Instance of `Admin`.
- `requestUtils`: Gutenberg request utils object.
- **Returns:** Promise resolving to the `Page` object.

#### 💡 Usage Example

```javascript
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
createTerm,
} from './taxonomies/index.js';
import { fillInXliffExportForm, getXliffRegex } from './xliff/index.js';
import { createTranslator, switchToUser } from './users/index.js';
import { getDownload, getStringFromFile } from './downloads/index.js';
import { getPlaywrightConfig } from './config/index.js';
import globalSetup from './setup/global.setup.js';
Expand All @@ -35,6 +36,8 @@ export {
fillInXliffExportForm,
getDownload,
getXliffRegex,
createTranslator,
switchToUser,
getStringFromFile,
getPlaywrightConfig,
globalSetup,
Expand Down
86 changes: 86 additions & 0 deletions src/users/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// @ts-check
import {
expect,
Admin,
RequestUtils,
} from '@wordpress/e2e-test-utils-playwright';
import { execSync } from 'child_process';

/**
* @typedef {import('@playwright/test').Page} Page
* @typedef {Object} User
* @property {string} username The user name.
* @property {string} password The user's password.
*/

/**
* Creates a translator user.
*
* @param {Array<string>} langSlugs Language slugs.
* @param {string} userName Optional. User name.
* Defaults to `XX-YY-translator`, where `XX` and `YY` are language slugs.
* @return {Promise<User&{id: number}>} Promise resolving to a user object containing ID, user name, and password.
*/
export async function createTranslator( langSlugs, userName = '' ) {
userName =
Copy link
Copy Markdown
Member

@Hug0-Drelon Hug0-Drelon Mar 4, 2026

Choose a reason for hiding this comment

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

I wouldn't check types here.
The reason is that we strongly document function in this package, with TS-like checks.
If someone passes dog shit, I expect the code to fail. Here it goes on silently.
Moreover, you coded a default value but didn't document it that way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The type check was my way to provide a default value. Improved with 61cdb97.

'' === userName
? `${ langSlugs.join( '-' ).toUpperCase() }-translator`
: userName;
const email = `${ userName.toLowerCase() }@example.com`;
const userId = parseInt(
execSync(
`npx wp-env run tests-cli wp user create ${ userName } ${ email } --role=editor --user_pass=password --porcelain`,
{ encoding: 'utf8' }
).trim(),
10
);

langSlugs.forEach( ( langSlug ) => {
execSync(
`npx wp-env run tests-cli wp user add-cap ${ userId } translate_${ langSlug }`,
{ encoding: 'utf8' }
);
} );

return { id: userId, username: userName, password: 'password' };
}

/**
* Switches to the given user.
* Inspired from https://github.com/WordPress/gutenberg/blob/9ee534a42cd546fc2da23ce0f31607467c78c94c/test/e2e/specs/editor/collaboration/fixtures/collaboration-utils.ts#L104.
*
* @param {User} user The user to switch to.
* @param {Admin} admin Instance of `Admin`.
* @param {RequestUtils} requestUtils Gutenberg request utils object.
* @return {Promise<Page>} Promise resolving to the `Page` object.
*/
export async function switchToUser( user, admin, requestUtils ) {
const translatorContext = await admin.browser.newContext( {
baseURL: requestUtils.baseURL,
} );
const page = await translatorContext.newPage();

await page.goto( '/wp-login.php' );
await page.locator( '#user_login' ).fill( user.username );
await page.locator( '#user_pass' ).fill( user.password );
await page.getByRole( 'button', { name: 'Log In' } ).click();
await page.waitForURL( '**/wp-admin/**' );

expect(
page.getByRole( 'menuitem', {
name: `Howdy, ${ user.username }`,
} )
).toBeVisible();

await page.waitForFunction( () => window?.wp?.data && window?.wp?.blocks );
await page.evaluate( () => {
window.wp.data
.dispatch( 'core/preferences' )
.set( 'core/edit-post', 'welcomeGuide', false );
window.wp.data
.dispatch( 'core/preferences' )
.set( 'core/edit-post', 'fullscreenMode', false );
} );
Comment thread
Screenfeed marked this conversation as resolved.

return page;
}