From eba4f5f1c1a22cea52c6605838edea556ef8c74d Mon Sep 17 00:00:00 2001
From: Fredrik Rombach Ekelund
Date: Thu, 8 Oct 2026 13:52:43 +0200
Subject: [PATCH 1/3] Redirect to the main screen on every activation
The redirect used to be set only on the first activation, before the
site had a BlogVault account. A bulk activation now stays on the plugin
list.
Co-Authored-By: Claude Opus 5.5
---
plugin/wp_actions.php | 2 ++
plugin/wp_admin.php | 7 +++--
tests/e2e/blueprint-menu.json | 8 +++++
tests/e2e/menu-steps.php | 55 +++++++++++++++++++++++++++++++++++
4 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/plugin/wp_actions.php b/plugin/wp_actions.php
index e9b69c05..b9685e8e 100644
--- a/plugin/wp_actions.php
+++ b/plugin/wp_actions.php
@@ -27,6 +27,8 @@ public function activate() {
} else {
WPCOMAccount::setup($this->settings);
}
+ // Every activation leads to the migration screen, not only the first.
+ $this->settings->updateOption($this->bvinfo->plug_redirect, 'yes');
}
public function deactivate() {
diff --git a/plugin/wp_admin.php b/plugin/wp_admin.php
index 487f2028..b6557df4 100755
--- a/plugin/wp_admin.php
+++ b/plugin/wp_admin.php
@@ -61,8 +61,11 @@ public function initHandler() {
if ($this->bvinfo->isActivateRedirectSet()) {
$this->settings->updateOption($this->bvinfo->plug_redirect, 'no');
- if (!wp_doing_ajax()) {
- wp_redirect($this->migrationScreenUrl());
+ // A bulk activation lands on the plugin list; it stays there.
+ if (!wp_doing_ajax() && !isset($_GET['activate-multi'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if (wp_redirect($this->migrationScreenUrl())) {
+ exit;
+ }
}
}
}
diff --git a/tests/e2e/blueprint-menu.json b/tests/e2e/blueprint-menu.json
index 49863bfb..3d126fbb 100644
--- a/tests/e2e/blueprint-menu.json
+++ b/tests/e2e/blueprint-menu.json
@@ -42,6 +42,14 @@
"step": "runPHP",
"code": " array( 'wpcom-migration' => 1 ) ) );
+ add_filter(
+ 'pre_http_request',
+ function () {
+ return array(
+ 'headers' => array(),
+ 'body' => '',
+ 'response' => array(
+ 'code' => 200,
+ 'message' => 'OK',
+ ),
+ 'cookies' => array(),
+ );
+ }
+ );
+ $plugin = 'wpcom-migration/wpcom_migration.php';
+ deactivate_plugins( $plugin, true );
+ update_option( 'wpcomredirect', 'no' );
+ $result = activate_plugin( $plugin );
+ delete_option( 'bvAccountsList' );
+ if ( is_wp_error( $result ) ) {
+ throw new RuntimeException( 'Reactivating failed: ' . $result->get_error_message() );
+ }
+ if ( 'yes' !== get_option( 'wpcomredirect' ) ) {
+ throw new RuntimeException( 'Reactivating should ask for the redirect again.' );
+ }
+ update_option( 'wpcomredirect', 'no' );
+ break;
+
+ case 'bulk-activation-stays':
+ $captured = null;
+ add_filter(
+ 'wp_redirect',
+ function ( $location ) use ( &$captured ) {
+ $captured = $location;
+ return '';
+ }
+ );
+ update_option( 'wpcomredirect', 'yes' );
+ $_GET['activate-multi'] = 'true';
+
+ $admin = new WPCOMWPAdmin( new WPCOMWPSettings(), new WPCOMWPSiteInfo() );
+ $admin->initHandler();
+
+ if ( null !== $captured ) {
+ throw new RuntimeException( 'A bulk activation should stay on the plugin list, got: ' . $captured );
+ }
+ if ( 'no' !== get_option( 'wpcomredirect' ) ) {
+ throw new RuntimeException( 'A bulk activation should use up the redirect.' );
+ }
+ break;
+
case 'multisite-subsite':
// The plugin's entry points on a network lead to the old screen
// in the network admin; a subsite has no row, since the Reprint
From 22ca4df0251234c74ced32519b70d67c7e509a52 Mon Sep 17 00:00:00 2001
From: Fredrik Rombach Ekelund
Date: Thu, 8 Oct 2026 13:52:43 +0200
Subject: [PATCH 2/3] Apply the design tweaks to the Reprint migration screen
A wider column without the logo header, a status pill above the heading
on the connected, set-up and ready screens, and new text per mode. The
WordPress.com button reads "Go to WordPress.com", carries an arrow, and
links to my.wordpress.com/sites. The login button reads "Connect
WordPress.com".
Co-Authored-By: Claude Opus 5.5
---
README.md | 18 ++--
plugin/connection/class-connect-page.php | 57 ++++--------
plugin/reprint/class-settings-page.php | 111 ++++++++++-------------
plugin/reprint/settings-page.css | 83 ++++++++++-------
tests/e2e/blueprint-connection.json | 4 -
tests/e2e/connection-steps.php | 54 ++++-------
tests/e2e/screen-steps.php | 49 +++++-----
7 files changed, 174 insertions(+), 202 deletions(-)
diff --git a/README.md b/README.md
index 8dfd95f0..91e59b2f 100644
--- a/README.md
+++ b/README.md
@@ -26,18 +26,20 @@ To activate a source checkout directly (without a build), run `composer install
## The Reprint migration screen
-`wp-admin/admin.php?page=wpcom-migration-status` (the plugin's one menu entry; `manage_options` to view; the `administrator` role to connect, disconnect, or provision through the routes; single-site only). It carries the old main screen's design: the WordPress mark, a serif heading and one centred column. The screen shows one mode at a time — one sentence saying where the site stands, at most one button, and the links that fit:
+`wp-admin/admin.php?page=wpcom-migration-status` (the plugin's one menu entry; `manage_options` to view; the `administrator` role to connect, disconnect, or provision through the routes; single-site only). The screen shows one mode at a time:
| Mode | When | Shows |
|---|---|---|
| The exporter runs on single sites only | Multisite | Nothing |
-| The export credential no longer matches this site | The site's salts changed | *Log in with WordPress.com*, or *Continue on WordPress.com* when connected |
-| The exporter is on until *time* | A migration is running | *Continue on WordPress.com* and *Disconnect*, when connected |
-| Connected as *email* | Logged in; the migration has not started | *Continue on WordPress.com*, a *Disconnect* link |
-| This site is set up and ready | Provisioned with an application password | A *Log in with WordPress.com* link |
-| Log in with your WordPress.com account | Fresh site | *Log in with WordPress.com* |
+| The export credential no longer matches this site | The site's salts changed | *Connect WordPress.com*, or *Go to WordPress.com* and *Disconnect* when connected |
+| Pill: *Site ready to migrate* | The exporter is on | *Go to WordPress.com* |
+| Pill: *Connected to WordPress.com* | Logged in; the exporter is off | *Go to WordPress.com*, a *Disconnect* link |
+| Pill: *Set up by WordPress.com* | Provisioned with an application password; the exporter is off | *Go to WordPress.com* |
+| Log in with your WordPress.com account to connect this site | Fresh site | *Connect WordPress.com* |
-`Settings_Page::mode()` picks the first that fits, top to bottom. The *Continue* link's target is filtered by `wpcom_migration_continue_url`.
+`Settings_Page::mode()` picks the first that fits, top to bottom. *Go to WordPress.com* links to `https://my.wordpress.com/sites`; the `wpcom_migration_continue_url` filter changes that.
+
+Every activation sends the administrator to the main screen on their next wp-admin page load, except a bulk activation, which stays on the plugin list.
The old BlogVault screen at `wp-admin/admin.php?page=wpcom-migration` is no longer in the sidebar. It stays reachable at its URL.
@@ -57,7 +59,7 @@ Which credential WordPress.com sets up depends on the host. Where `openssl_verif
**Application password** — where `GET /wp-json/` lists `authentication.application-passwords` (core: `is_ssl()` true and no plugin has switched them off). WordPress.com sends the administrator once to `wp-admin/authorize-application.php?app_name=Migrate+to+WordPress.com&app_id=&success_url=…&reject_url=…`; core redirects to `success_url` with `site_url`, `user_login` and `password`. WordPress.com then checks the password with `GET /wp-json/wp/v2/users/me` (a 401 here means the server strips the `Authorization` header — Apache CGI without the rewrite rule WordPress 5.6 added; saving Permalinks regenerates it), installs and activates the plugin (`POST /wp-json/wp/v2/plugins {"slug":"wpcom-migration","status":"active"}`; an older copy is replaced by `PUT …/plugins/wpcom-migration/wpcom_migration {"status":"inactive"}`, `DELETE`, then the `POST`, since core has no update route), calls the two routes, exports, and revokes the password (`GET …/users/me/application-passwords/introspect`, then `DELETE …/application-passwords/`). No login on the site.
-**Jetpack connection** — everywhere else, including sites where Wordfence or another security plugin has disabled application passwords. The administrator installs the plugin from the plugin directory and presses *Log in with WordPress.com* on the screen; the connection package registers the site and sends them to WordPress.com to authorize, then back. A site that already runs Jetpack is connected at once for the account that connected Jetpack — the connection is shared. WordPress.com then calls the routes signed with the user token.
+**Jetpack connection** — everywhere else, including sites where Wordfence or another security plugin has disabled application passwords. The administrator installs the plugin from the plugin directory and presses *Connect WordPress.com* on the screen; the connection package registers the site and sends them to WordPress.com to authorize, then back. A site that already runs Jetpack is connected at once for the account that connected Jetpack — the connection is shared. WordPress.com then calls the routes signed with the user token.
Connection events fire `wpcom_migration_connection_event`, exporter events `wpcom_migration_reprint_export_event`; none carries a token, secret or signature.
diff --git a/plugin/connection/class-connect-page.php b/plugin/connection/class-connect-page.php
index 42350b44..d4bd8a45 100644
--- a/plugin/connection/class-connect-page.php
+++ b/plugin/connection/class-connect-page.php
@@ -180,25 +180,23 @@ private function redirect_with_notice( $result, $code = '' ) {
}
/**
- * Renders the login form: the screen's primary button, or a plain link.
- *
- * @param bool $primary Whether the submit is the primary button.
+ * Renders the login form, with the terms it agrees to.
*/
- public function render_connect_button( $primary ) {
+ public function render_connect_button() {
?>
plugin_file ),
- array( 'wpcom-migration-variables', 'wpcom-migration-fonts', 'dashicons' ),
+ array( 'wpcom-migration-variables', 'wpcom-migration-fonts' ),
$version
);
}
@@ -220,8 +220,7 @@ public function remove_admin_notices() {
}
/**
- * Renders the screen: the old main screen's design, with the mode where
- * that screen had its email form.
+ * Renders the screen: one centred column holding the mode.
*/
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
@@ -238,17 +237,9 @@ public function render_page() {
echo '';
}
?>
-
-
connection_section;
switch ( $mode ) {
case self::MODE_BLOCKED:
+ $this->render_pill_and_heading();
$this->render_sentence( __( 'The exporter runs on single sites only, so this site can\'t be migrated from here.', 'wpcom-migration' ) );
return;
case self::MODE_BROKEN:
+ $this->render_pill_and_heading();
$this->render_sentence( __( 'The export credential no longer matches this site — its security salts changed. Start the migration again on WordPress.com.', 'wpcom-migration' ) );
if ( null !== $section && $user_connected ) {
- $section->render_continue_button( true );
+ $section->render_continue_button();
$section->render_disconnect_link();
} elseif ( null !== $section ) {
- $section->render_connect_button( true );
+ $section->render_connect_button();
}
return;
case self::MODE_READY:
- $this->render_sentence(
- sprintf(
- /* translators: %s: time of day. */
- __( 'The exporter is on until %s. The migration runs from WordPress.com; each export keeps it on for another hour.', 'wpcom-migration' ),
- self::window_closes_at( $state )
- )
- );
- if ( null !== $section && $user_connected ) {
- $section->render_continue_button( false );
- $section->render_disconnect_link();
+ $this->render_pill_and_heading( __( 'Site ready to migrate', 'wpcom-migration' ), 'info' );
+ $this->render_sentence( __( 'Your site is now ready to be copied by WordPress.com. Your site stays online and unchanged while it\'s copied.', 'wpcom-migration' ) );
+ $this->render_sentence( __( 'Keep this plugin active until the migration is done. We email you when it\'s finished.', 'wpcom-migration' ) );
+ if ( null !== $section ) {
+ $section->render_continue_button();
}
return;
case self::MODE_CONNECTED_WAITING:
- $email = null !== $section ? \Automattic\WPCOM_Migration\Connect_Page::connected_email() : null;
- if ( null !== $email ) {
- $this->render_sentence_html(
- sprintf(
- /* translators: %s: WordPress.com account email address, in bold. */
- esc_html__( 'Connected as %s. WordPress.com sets up the exporter when the migration starts.', 'wpcom-migration' ),
- '' . esc_html( $email ) . ''
- )
- );
- } else {
- $this->render_sentence( __( 'Connected to WordPress.com. WordPress.com sets up the exporter when the migration starts.', 'wpcom-migration' ) );
- }
+ $this->render_pill_and_heading( __( 'Connected to WordPress.com', 'wpcom-migration' ), 'success' );
+ $this->render_ready_to_migrate();
if ( null !== $section ) {
- $section->render_continue_button( true );
+ $section->render_continue_button();
$section->render_disconnect_link();
}
return;
case self::MODE_PROVISIONED_WAITING:
- $this->render_sentence_html(
- '' . esc_html__( 'This site is set up and ready.', 'wpcom-migration' ) . ' '
- . esc_html__( 'The exporter stays off until the migration starts on WordPress.com.', 'wpcom-migration' )
- );
+ $this->render_pill_and_heading( __( 'Set up by WordPress.com', 'wpcom-migration' ), 'info' );
+ $this->render_ready_to_migrate();
if ( null !== $section ) {
- $section->render_connect_button( false );
+ $section->render_continue_button();
}
return;
case self::MODE_NEEDS_CONNECTING:
default:
- $this->render_sentence( __( 'Log in with your WordPress.com account so WordPress.com can read this site and migrate it. Nothing is copied until you start the migration there.', 'wpcom-migration' ) );
+ $this->render_pill_and_heading();
+ $this->render_sentence( __( 'WordPress.com copies your posts, pages, media, themes, plugins, and settings. Your site stays online and unchanged while we copy it.', 'wpcom-migration' ) );
+ $this->render_sentence( __( 'Log in with your WordPress.com account to connect this site. Nothing is copied until you start the migration on WordPress.com.', 'wpcom-migration' ) );
if ( null !== $section ) {
- $section->render_connect_button( true );
+ $section->render_connect_button();
}
return;
}
}
/**
- * Renders the mode's one sentence.
+ * Renders the heading, with a status pill above it when given one.
*
- * @param string $text Plain text.
+ * @param string $pill Pill text, or '' for none.
+ * @param string $tone success (green) or info (blue).
*/
- private function render_sentence( $text ) {
- $this->render_sentence_html( esc_html( $text ) );
+ private function render_pill_and_heading( $pill = '', $tone = 'info' ) {
+ if ( '' !== $pill ) {
+ echo '' . esc_html( $pill ) . '
';
+ }
+ echo '' . esc_html__( 'Migrate your site to WordPress.com', 'wpcom-migration' ) . '
';
}
/**
- * Renders the mode's one sentence from markup the caller has escaped.
- *
- * @param string $html Escaped HTML.
+ * Renders the sentence the two waiting modes share.
*/
- private function render_sentence_html( $html ) {
- echo '' . $html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by the caller.
+ private function render_ready_to_migrate() {
+ $this->render_sentence(
+ sprintf(
+ /* translators: %s: the site's address, without the scheme. */
+ __( '%s is ready to migrate. The migration runs from WordPress.com, so there\'s nothing else to do here.', 'wpcom-migration' ),
+ untrailingslashit( preg_replace( '#^https?://#i', '', home_url() ) )
+ )
+ );
}
/**
- * The time of day the exporter turns itself off, in the site's format.
+ * Renders one paragraph of the mode.
*
- * @param array $state Exporter::get_state(), with the window open.
- * @return string
+ * @param string $text Plain text.
*/
- private static function window_closes_at( array $state ) {
- $expires = $state['window_expires_at'] + (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
- return date_i18n( get_option( 'time_format' ), $expires );
+ private function render_sentence( $text ) {
+ echo '' . esc_html( $text ) . '
';
}
}
diff --git a/plugin/reprint/settings-page.css b/plugin/reprint/settings-page.css
index 0f7df3be..18229ec7 100644
--- a/plugin/reprint/settings-page.css
+++ b/plugin/reprint/settings-page.css
@@ -1,5 +1,5 @@
/*
- * The Reprint migration screen: the old main screen's design, one column.
+ * The Reprint migration screen: one centred column.
*
* variables.css supplies the palette, fonts.css the Recoleta face. The old
* screen's style.css is deliberately not a dependency — it is that screen's
@@ -19,29 +19,21 @@ body {
display: none;
}
-.wpcom-migration-header {
- display: flex;
- padding: 20px;
-}
-
-.wpcom-migration-header__wpcom-logo span {
- width: 24px;
- height: 24px;
- font-size: 24px;
-}
-
.wpcom-migration-container {
- box-sizing: border-box;
- max-width: 476px;
+ box-sizing: content-box;
+ max-width: 660px;
margin: 0 auto;
padding: 40px 20px;
- font-size: 14px;
- color: var( --wpcom-migration-gray-70 );
+ font-size: 15px;
+ line-height: 24px;
+ color: var( --wpcom-migration-gray-100 );
text-underline-offset: 4px;
}
.wpcom-migration-container h1 {
- margin: 0;
+ max-width: 440px;
+ margin: 0 0 16px;
+ padding: 0;
color: var( --wpcom-migration-gray-100 );
font-family: var( --wpcom-migration-heading-font-family );
font-size: 34px;
@@ -50,8 +42,9 @@ body {
}
.wpcom-migration-container p {
- margin: 8px 0;
+ margin: 16px 0;
font-size: inherit;
+ line-height: inherit;
}
.wpcom-migration-container a:not( .wpcom-migration-button ) {
@@ -63,24 +56,45 @@ body {
text-decoration: none;
}
-.wpcom-migration-guidance {
- margin-top: 24px;
+.wpcom-migration-container .wpcom-migration-pill {
+ display: inline-block;
+ margin: 0 0 16px;
+ padding: 2px 8px;
+ border-radius: 6px;
+ font-size: 12px;
+ line-height: 20px;
+}
+
+.wpcom-migration-pill--success {
+ color: #00450c;
+ background: #b8f5c8;
+}
+
+.wpcom-migration-pill--info {
+ color: #0b2a6f;
+ background: #dfe8fd;
}
.wpcom-migration-button {
display: inline-block;
- margin: 4px 0;
- padding: 12px 24px;
+ margin: 0;
+ padding: 10px 12px;
border: 0;
- border-radius: 4px;
+ border-radius: 2px;
color: var( --wpcom-migration-white );
background: var( --wpcom-migration-primary-bg );
- font-size: 14px;
- line-height: 1.4;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 20px;
text-decoration: none;
cursor: pointer;
}
+.wpcom-migration-button__icon {
+ margin-left: 2px;
+ vertical-align: 0;
+}
+
.wpcom-migration-container a.wpcom-migration-button:hover,
.wpcom-migration-button:hover,
.wpcom-migration-button:focus {
@@ -93,18 +107,25 @@ body {
border: 0;
background: none;
color: var( --wpcom-migration-primary-bg );
- font-size: inherit;
+ font-size: 13px;
+ line-height: 20px;
text-decoration: underline;
+ text-underline-offset: 4px;
cursor: pointer;
}
-.wpcom-migration-link--delete {
- color: #b32d2e;
+.wpcom-migration-link--quiet {
+ color: var( --wpcom-migration-gray-100 );
}
-.wpcom-migration-description {
+.wpcom-migration-link:hover {
+ color: var( --wpcom-migration-link-hover-color );
+ text-decoration: none;
+}
+
+.wpcom-migration-container .wpcom-migration-description {
color: var( --wpcom-migration-gray-50 );
- font-size: 12px;
+ font-size: 13px;
line-height: 20px;
}
@@ -115,7 +136,7 @@ body {
@media ( min-width: 961px ) {
.wpcom-migration-container {
- padding-top: 100px;
+ padding-top: 148px;
}
.wpcom-migration-container h1 {
diff --git a/tests/e2e/blueprint-connection.json b/tests/e2e/blueprint-connection.json
index 4cdcfc2d..72cf6783 100644
--- a/tests/e2e/blueprint-connection.json
+++ b/tests/e2e/blueprint-connection.json
@@ -54,10 +54,6 @@
"step": "runPHP",
"code": "', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'wpcom-migration-status', $step );
break;
@@ -346,8 +345,8 @@ function ( $location ) {
$_GET[ Connect_Page::CODE_QUERY_ARG ] = 'e2e_blocked';
$html = wpcom_migration_e2e_render_connect_page();
wpcom_migration_e2e_expect_contains( $html, 'could not register', $step );
- if ( strpos( $html, 'could not register' ) > strpos( $html, 'class="wpcom-migration-header"' ) ) {
- throw new RuntimeException( "Step '$step': the notice should sit above the screen's header, where wp-admin puts notices." );
+ if ( strpos( $html, 'could not register' ) > strpos( $html, 'class="wpcom-migration-container"' ) ) {
+ throw new RuntimeException( "Step '$step': the notice should sit above the screen's column, where wp-admin puts notices." );
}
wpcom_migration_e2e_expect_contains( $html, 'e2e_blocked', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'Blocked by the e2e scenario', $step );
@@ -386,35 +385,22 @@ function ( $location ) {
case 'render-connected':
wpcom_migration_e2e_expect_mode( Settings_Page::MODE_CONNECTED_WAITING, $step );
$html = wpcom_migration_e2e_render_connect_page();
- wpcom_migration_e2e_expect_contains( $html, 'Connected as e2e@example.com', $step );
- if ( 1 !== substr_count( $html, 'Connected as e2e@example.com' ) ) {
- throw new RuntimeException( "Step '$step': expected 'Connected as e2e@example.com' exactly once: " . substr( $html, 0, 400 ) );
- }
- wpcom_migration_e2e_expect_contains( $html, 'sets up the exporter when the migration starts', $step );
- wpcom_migration_e2e_expect_contains( $html, 'Continue on WordPress.com', $step );
- wpcom_migration_e2e_expect_contains( $html, 'https://wordpress.com/setup/site-migration?from=' . rawurlencode( home_url() ), $step );
+ wpcom_migration_e2e_expect_contains( $html, 'wpcom-migration-pill--success">Connected to WordPress.com
', $step );
+ wpcom_migration_e2e_expect_contains( $html, untrailingslashit( preg_replace( '#^https?://#i', '', home_url() ) ) . ' is ready to migrate.', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'e2e@example.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Go to WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'href="https://my.wordpress.com/sites"', $step );
wpcom_migration_e2e_expect_primary_count( $html, 1, $step );
wpcom_migration_e2e_expect_contains( $html, 'value="' . Connect_Page::DISCONNECT_ACTION . '"', $step );
- wpcom_migration_e2e_expect_contains( $html, 'wpcom-migration-link--delete', $step );
+ wpcom_migration_e2e_expect_contains( $html, 's connection to WordPress.com.', $step );
wpcom_migration_e2e_expect_not_contains( $html, (string) WPCOM_MIGRATION_E2E_BLOG_ID, $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Log in with WordPress.com', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Connect WordPress.com', $step );
// The blog ID lives on the by-hand screen now.
$manual_html = wpcom_migration_e2e_render_manual_page();
wpcom_migration_e2e_expect_contains( $manual_html, (string) WPCOM_MIGRATION_E2E_BLOG_ID, $step );
break;
- case 'render-connected-without-user-data':
- delete_transient( 'jetpack_connected_user_data_1' );
- // Cache the failure the package would otherwise record after a
- // remote call, so the render is offline and quick.
- set_transient( 'jetpack_connected_user_data_1', 'error', 5 * MINUTE_IN_SECONDS );
- wpcom_migration_e2e_expect_mode( Settings_Page::MODE_CONNECTED_WAITING, $step );
- $html = wpcom_migration_e2e_render_connect_page();
- wpcom_migration_e2e_expect_contains( $html, 'Connected to WordPress.com', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Connected as', $step );
- break;
-
default:
throw new RuntimeException( 'Unknown connection step: ' . $step );
}
diff --git a/tests/e2e/screen-steps.php b/tests/e2e/screen-steps.php
index 09eb48f2..11332c5b 100644
--- a/tests/e2e/screen-steps.php
+++ b/tests/e2e/screen-steps.php
@@ -8,6 +8,7 @@
* @package wpcom-migration
*/
+use Automattic\WPCOM_Migration\Connect_Page;
use Automattic\WPCOM_Migration\Reprint\Exporter;
use Automattic\WPCOM_Migration\Reprint\Manual_Page;
use Automattic\WPCOM_Migration\Reprint\Settings_Page;
@@ -35,9 +36,11 @@ function wpcom_migration_e2e_screen_step( $step ) {
case 'render-unconfigured':
wpcom_migration_e2e_expect_mode( Settings_Page::MODE_NEEDS_CONNECTING, $step );
$html = wpcom_migration_e2e_render( $page );
- wpcom_migration_e2e_expect_contains( $html, 'Log in with your WordPress.com account', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Connect this site to WordPress.com', $step );
- wpcom_migration_e2e_expect_contains( $html, 'Log in with WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Log in with your WordPress.com account to connect this site', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Connect WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'By connecting, you agree to the', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Log in with WordPress.com', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'wpcom-migration-pill', $step );
wpcom_migration_e2e_expect_primary_count( $html, 1, $step );
wpcom_migration_e2e_expect_not_contains( $html, 'Set up Reprint manually', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'id="wpcom-migration-reprint-secret"', $step );
@@ -45,10 +48,10 @@ function wpcom_migration_e2e_screen_step( $step ) {
wpcom_migration_e2e_expect_not_contains( $html, 'wpcom-migration-status', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'WordPress.com connection', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'Not configured yet', $step );
- wpcom_migration_e2e_expect_contains( $html, 'class="wpcom-migration-header"', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'class="wpcom-migration-header"', $step );
wpcom_migration_e2e_expect_contains( $html, 'class="wpcom-migration-container"', $step );
wpcom_migration_e2e_expect_contains( $html, 'Migrate your site to WordPress.com', $step );
- wpcom_migration_e2e_expect_contains( $html, 'WordPress.com copies your posts, pages, media and settings', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'WordPress.com copies your posts, pages, media, themes, plugins, and settings', $step );
wpcom_migration_e2e_expect_not_contains( $html, '', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'Reprint migration', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'button-primary', $step );
@@ -60,8 +63,8 @@ function wpcom_migration_e2e_screen_step( $step ) {
// No connection bootstrap: the mode renders without its controls.
$bare = new Settings_Page( WP_PLUGIN_DIR . '/wpcom-migration/wpcom_migration.php' );
$html = wpcom_migration_e2e_render( $bare );
- wpcom_migration_e2e_expect_contains( $html, 'Log in with your WordPress.com account', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Log in with WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Log in with your WordPress.com account to connect this site', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Connect WordPress.com', $step );
wpcom_migration_e2e_expect_primary_count( $html, 0, $step );
wpcom_migration_e2e_expect_not_contains( $html, 'id="wpcom-migration-reprint-secret"', $step );
break;
@@ -90,12 +93,13 @@ function wpcom_migration_e2e_screen_step( $step ) {
}
wpcom_migration_e2e_expect_mode( Settings_Page::MODE_PROVISIONED_WAITING, $step );
$html = wpcom_migration_e2e_render( $page );
- wpcom_migration_e2e_expect_contains( $html, 'This site is set up and ready.', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Set up by WordPress.com', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Nothing to do here', $step );
- // The login stays, demoted to a link.
- wpcom_migration_e2e_expect_contains( $html, 'Log in with WordPress.com', $step );
- wpcom_migration_e2e_expect_primary_count( $html, 0, $step );
+ wpcom_migration_e2e_expect_contains( $html, 'wpcom-migration-pill--info">Set up by WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'is ready to migrate. The migration runs from WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Go to WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'href="https://my.wordpress.com/sites"', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Connect WordPress.com', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, Connect_Page::DISCONNECT_ACTION, $step );
+ wpcom_migration_e2e_expect_primary_count( $html, 1, $step );
wpcom_migration_e2e_expect_not_contains( $html, 'id="wpcom-migration-reprint-api-url"', $step );
wpcom_migration_e2e_expect_not_contains( $html, '
Export secret
', $step );
wpcom_migration_e2e_expect_not_contains( $html, 'Turn the exporter on', $step );
@@ -193,7 +197,7 @@ function wpcom_migration_e2e_screen_step( $step ) {
$html = wpcom_migration_e2e_render( $page );
wpcom_migration_e2e_expect_contains( $html, 'no longer matches this site', $step );
wpcom_migration_e2e_expect_contains( $html, 'its security salts changed', $step );
- wpcom_migration_e2e_expect_contains( $html, 'Log in with WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Connect WordPress.com', $step );
wpcom_migration_e2e_expect_primary_count( $html, 1, $step );
wpcom_migration_e2e_expect_not_contains( $html, 'Invalid: the site', $step );
break;
@@ -210,12 +214,13 @@ function wpcom_migration_e2e_screen_step( $step ) {
}
wpcom_migration_e2e_expect_mode( Settings_Page::MODE_READY, $step );
$html = wpcom_migration_e2e_render( $page );
- wpcom_migration_e2e_expect_contains( $html, 'The exporter is on until', $step );
- wpcom_migration_e2e_expect_contains( $html, 'each export keeps it on for another hour', $step );
- wpcom_migration_e2e_expect_primary_count( $html, 0, $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Log in with WordPress.com', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'Continue on WordPress.com', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'This site is set up and ready', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'wpcom-migration-pill--info">Site ready to migrate', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Your site is now ready to be copied by WordPress.com', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Keep this plugin active until the migration is done', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Go to WordPress.com', $step );
+ wpcom_migration_e2e_expect_primary_count( $html, 1, $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Connect WordPress.com', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Set up by WordPress.com', $step );
break;
case 'disable':
@@ -230,8 +235,8 @@ function wpcom_migration_e2e_screen_step( $step ) {
}
wpcom_migration_e2e_expect_mode( Settings_Page::MODE_PROVISIONED_WAITING, $step );
$html = wpcom_migration_e2e_render( $page );
- wpcom_migration_e2e_expect_contains( $html, 'This site is set up and ready', $step );
- wpcom_migration_e2e_expect_not_contains( $html, 'The exporter is on until', $step );
+ wpcom_migration_e2e_expect_contains( $html, 'Set up by WordPress.com', $step );
+ wpcom_migration_e2e_expect_not_contains( $html, 'Site ready to migrate', $step );
break;
case 'discard-secret':
From b50527a379a5e56ba6afc0cdb44cbec35eb8aeb4 Mon Sep 17 00:00:00 2001
From: Fredrik Rombach Ekelund
Date: Thu, 8 Oct 2026 14:02:17 +0200
Subject: [PATCH 3/3] Count primary buttons by their exact class in the e2e
The arrow's wpcom-migration-button__icon class also contains
wpcom-migration-button, so each Go to WordPress.com button counted twice.
Co-Authored-By: Claude Opus 5.5
---
tests/e2e/connection-steps.php | 4 ++--
tests/e2e/screen-steps.php | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/tests/e2e/connection-steps.php b/tests/e2e/connection-steps.php
index 89947348..09a0cbda 100644
--- a/tests/e2e/connection-steps.php
+++ b/tests/e2e/connection-steps.php
@@ -614,12 +614,12 @@ function wpcom_migration_e2e_expect_mode( $expected, $step ) {
* buttons. The screen's primary is the branded button, not wp-admin's.
*
* @param string $html Rendered markup.
- * @param int $expected Expected count of 'wpcom-migration-button'.
+ * @param int $expected Expected count of class="wpcom-migration-button".
* @param string $step Name of the step, for the error message.
* @throws RuntimeException When the count differs.
*/
function wpcom_migration_e2e_expect_primary_count( $html, $expected, $step ) {
- $count = substr_count( $html, 'wpcom-migration-button' );
+ $count = substr_count( $html, 'class="wpcom-migration-button"' );
if ( $expected !== $count ) {
throw new RuntimeException( "Step '$step': expected $expected primary button(s), found $count." );
}
diff --git a/tests/e2e/screen-steps.php b/tests/e2e/screen-steps.php
index 11332c5b..0e72a713 100644
--- a/tests/e2e/screen-steps.php
+++ b/tests/e2e/screen-steps.php
@@ -444,12 +444,12 @@ function wpcom_migration_e2e_expect_mode( $expected, $step ) {
* buttons. The screen's primary is the branded button, not wp-admin's.
*
* @param string $html Rendered markup.
- * @param int $expected Expected count of 'wpcom-migration-button'.
+ * @param int $expected Expected count of class="wpcom-migration-button".
* @param string $step Name of the step, for the error message.
* @throws RuntimeException When the count differs.
*/
function wpcom_migration_e2e_expect_primary_count( $html, $expected, $step ) {
- $count = substr_count( $html, 'wpcom-migration-button' );
+ $count = substr_count( $html, 'class="wpcom-migration-button"' );
if ( $expected !== $count ) {
throw new RuntimeException( "Step '$step': expected $expected primary button(s), found $count." );
}