Skip to content

Playwright versus WordPress’s ‘admin email confirmation’ screen — how automation can clear the 6-month gate

If you drive the WordPress admin via Playwright for long enough, one day a screen you’ve never seen before will appear after login, and everything downstream stops working.

Is admin@example.com still the correct admin email address?

[ Yes, the email is correct ]
[ Change the address ]

That’s WordPress’s admin email confirmation screen. Roughly every six months, after the admin user logs in, this confirmation screen gets injected — standard behavior since WP 4.9. A human just clicks once. An automation script can’t see it without explicit handling.

Why automation gets stuck

A straightforward Playwright login looks like:

page.fill('#user_login', user)
page.fill('#user_pass', pwd)
page.click('input[type="submit"]')
page.wait_for_load_state('domcontentloaded')
# Assumes we're on the dashboard
page.goto('/wp-admin/plugins.php')

But on a “confirmation day,” the URL right after wait_for_load_state is something like /wp-admin/profile.php?...action=confirm_admin_email... — the confirmation screen. You thought you were navigating to the plugins page, but the DOM you expected isn’t there. Subsequent selectors fail, and everything downstream cascades into failure.

A specific selector identifies the screen

WordPress’s confirmation screen has a uniquely-named submit button:

<input type="submit"
       name="correct-admin-email"
       value="Yes, the email is correct" />

If input[name="correct-admin-email"] exists on the page, you’re on the confirmation screen. The same selector serves as both the detection signal and the click target, so handling is only a few lines:

admin_email_confirm = page.locator(
    'input[type="submit"][name="correct-admin-email"]'
)
if admin_email_confirm.count() > 0:
    logger.info("Confirmation screen detected — clicking 'email is correct'")
    admin_email_confirm.first.click()
    page.wait_for_load_state('domcontentloaded', timeout=30000)

Insert this after the post-login wait_for_load_state and before subsequent navigation. It runs transparently whether the screen appears or not.

Handler omission via code duplication

Internally, we had four places running this same login handling:

  • The main maintenance login flow ✓
  • The login used by visual_check (pre/post screenshot capture) ✓
  • The login used for thumbnail capture ✓
  • The login used by browser-based residual update (the path that handles plugins with proprietary updaters) ❌

The last one — added later — forgot to copy the confirmation-screen handler from the existing three. The downstream effect: when the confirmation screen showed up, automated updates for ACF Pro / Yoast SEO Premium / WP Rocket / Elementor Pro could be missed entirely.

A similar structural pattern appeared in our seven-format SSH private-key compat loader work: when the same logic lives in multiple copies, additions to one copy tend to drop functionality from others. The realistic mitigations are either deduplicating after the fact, or — at minimum — running tests that exercise the same behavior across every duplicate.

Regression-proofing through tests

Three tests went in with the fix:

  1. Confirmation screen present → click happens: With a mock returning the confirmation HTML, the click() call fires
  2. Confirmation screen absent → click does NOT happen: With normal dashboard HTML, the click is suppressed (no false positive)
  3. Click happens but auth still fails → still treated as a login failure: If the confirmation gets clicked but the dashboard never loads, the function does not incorrectly report success

Tests 2 and 3 specifically guard against “the new handler introducing a different kind of misbehavior.” Negative tests that run alongside a feature addition are quietly effective at preventing regressions.

Takeaway — WordPress’s “six-month trap”

Long-running Playwright operations against WordPress admin keep encountering screens you don’t see during implementation but that appear half a year later and break everything. The admin-email confirmation screen is the canonical example: it’s invisible during testing on a Monday, but on the day it appears, the whole flow falls over.

Two practices keep WordPress + Playwright automation stable for the long haul: write the confirmation screen into the code explicitly, and if your login code is duplicated across multiple call sites, make sure the same handling is in every one.