wp-admin-browser

Use when a WordPress admin panel needs real browser interaction via Chrome DevTools MCP — logging in, navigating admin menus, clicking buttons, filling and submitting forms, creating/editing/deleting

SKILL.md

GitHub

WordPress Admin via Browser (Chrome DevTools MCP)

Model note: Primarily MCP tool calls — navigate, fill, click. haiku works for simple CRUD flows. Complex UI sequences (multi-step forms, dynamic AJAX state) use sonnet to handle unexpected DOM states.

When to use

  • "Log in to the WordPress admin", "navigate to Settings > General", "create a test user".
  • "Submit a form in WP admin", "upload a file via the media library", "save plugin settings".
  • "Create a temporary admin account for testing instead of using the main admin user".
  • "Verify a feature works end-to-end through the browser UI".

Not for: Headless automated testing without a real browser — use wp-plugin-testing. PHP code changes and plugin logic that don't require browser interaction.

Core Rules — Non-Negotiable

  1. Never touch the main admin user. Always create a temporary admin for testing.
  2. All data operations go through the browser UI. No WP-CLI, no direct DB, no REST API calls to mutate data — use WordPress forms.
  3. Navigate via menus, not hardcoded URLs. Click the menu item; don't jump straight to /wp-admin/users.php?action=....
  4. Use fill_form + click for all inputs. Never skip the form and post directly.

Step 1 — Log In

// POST to wp-login.php via fetch (fastest, no UI needed for login itself)
async () => {
  const res = await fetch('/wp-login.php', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      log: 'YOUR_USER',
      pwd: 'YOUR_PASS',
      'wp-submit': 'Log In',
      redirect_to: '/wp-admin/',
      testcookie: '1',
    }),
    credentials: 'include',
    redirect: 'follow',
  });
  return { ok: res.ok, url: res.url };
}

Login via fetch is acceptable because it's a pure auth step — no data mutation.


Step 2 — Create a Temporary Admin User

Always create a temp user before any testing that requires admin actions.

Navigate to Users → Add New via menu clicks, not direct URL.

Admin menu → Users → Add New

Fill the form using fill_form:

Field Value
Username tmp_admin_<timestamp>
Email tmp+<timestamp>@example.com
First Name Temp
Last Name Admin
Role Administrator
Password strong generated password
Send notification unchecked

After testing: delete the temp user via Users list → hover → Delete.


Step 3 — Get a REST Nonce (for read-only API calls only)

async () => {
  return fetch('/wp-admin/admin-ajax.php?action=rest-nonce', {
    credentials: 'include',
  }).then(r => r.text());
}

Use nonce only for GET requests to read data. All mutations go through WP forms.


Browser Interaction Rules

Navigation

✅ Click: Admin menu → Submenu item
❌ Never: navigate_page to hardcoded /wp-admin/edit.php?post_type=...

Forms

✅ fill_form on visible fields → click Submit button
❌ Never: fetch POST directly to admin-post.php / admin-ajax.php for data changes
❌ Never: wp eval or wp post create via CLI for browser-visible operations

Menus

Always wait for the page to load after each menu click before interacting with the next element.


Data Operation Checklist

Operation Method
Create post/page Posts → Add New → fill form → Publish
Update user Users → find user → Edit → fill form → Update
Delete item List view → hover row → Delete (confirm dialog)
Change setting Settings menu → fill field → Save Changes
Install plugin Plugins → Add New → Search → Install → Activate

Common Mistakes

Mistake Fix
Using main admin for destructive tests Create temp admin first
Navigating directly to ?action=delete&id=X Use list UI → Delete link
Using wp user create CLI to seed browser session Use Add New User form
Skipping fill_form and posting via fetch Always fill the visible form
Leaving temp admin after testing Delete via Users list when done

JS State Verification

Use evaluate_script to inspect JavaScript state without touching the UI.

Check scripts loaded + globals present

() => ({
    // Verify a localized WP global is available
    myPlugin: typeof window.MY_PLUGIN,
    keys: Object.keys(window.MY_PLUGIN || {}),
    // Verify a library bundle loaded
    driverLoaded: !!window.driver?.js?.driver,
    // Check current URL
    url: location.href,
    hash: location.hash,
})

If typeof window.MY_PLUGIN === 'undefined' the script may not be enqueued for this screen, or the page is in maintenance mode (document.title === 'Maintenance').

SPA pages — always wait for React to render

// Wrong: query DOM immediately after navigate
() => document.querySelector('.spa-element')  // returns null

// Right: wrap in setTimeout
() => new Promise(r => setTimeout(() => r(
    !!document.querySelector('.spa-element')
), 2000))

Verify CSS selectors before committing

() => {
    const selectors = [
        '#my-id',
        '.class-one.class-two',
        '.border-\\[\\#F0EDFB\\]',   // Tailwind escaped
    ];
    return selectors.map(s => ({
        selector: s,
        found: !!document.querySelector(s),
        count: document.querySelectorAll(s).length,
    }));
}

Always test on the page where the selector is expected to exist. A selector for the orders page returns false on the dashboard — that's not a bug.

localStorage testing

// Clear feature flags / completion state before testing
() => {
    Object.keys(localStorage)
        .filter(k => k.startsWith('myplugin_'))
        .forEach(k => localStorage.removeItem(k));
    return 'cleared';
}

// Read a specific key after an action
() => localStorage.getItem('myplugin_feature_completed')  // "true" or null

Detect maintenance mode / redirect loop

If navigate_page lands on a blank page or unexpected content:

() => ({ title: document.title, url: location.href })
// "Maintenance" title = WP in maintenance mode (.maintenance file or plugin)
// Redirected to /wp-login.php = session expired — re-login via fetch

Re-login when session expires

async () => {
    const res = await fetch('/wp-login.php', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
            log: 'admin', pwd: 'admin',
            'wp-submit': 'Log In',
            redirect_to: '/wp-admin/',
            testcookie: '1',
        }),
        credentials: 'include',
        redirect: 'follow',
    });
    return { ok: res.ok, url: res.url };
}

References


Cleanup

After every test session:

  1. Log in as main admin (or existing admin).
  2. Users → find all tmp_admin_* users → bulk delete.
  3. Do not reassign their content (temp users have no content).