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:
- Confirmation screen present → click happens: With a mock returning the confirmation HTML, the
click()call fires - Confirmation screen absent → click does NOT happen: With normal dashboard HTML, the click is suppressed (no false positive)
- 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.