Add Client::move_pointer_to helper

Extract the move-pointer-and-capture-enter-serial pattern into a shared
Client method so it can be reused across tests. Use it in the wl_pointer
cursor tests and in the ext_image_copy_capture cursor capture test.
diff --git a/include/in_process_server.h b/include/in_process_server.h
index 04a897d..a1e3691 100644
--- a/include/in_process_server.h
+++ b/include/in_process_server.h
@@ -338,6 +338,12 @@
     void add_pointer_motion_notification(PointerMotionNotifier const& on_motion);
     void add_pointer_button_notification(PointerButtonNotifier const& on_button);
 
+    /// Move \p pointer to the absolute coordinates (x, y) and return the serial
+    /// of the resulting wl_pointer.enter event. Fails the test if no enter
+    /// event is received. The returned serial is required by many wl_pointer
+    /// requests, most notably wl_pointer.set_cursor.
+    uint32_t move_pointer_to(Pointer& pointer, int x, int y);
+
     void dispatch_until(
         std::function<bool()> const& predicate,
         std::chrono::seconds timeout = helpers::a_long_time());
diff --git a/src/in_process_server.cpp b/src/in_process_server.cpp
index 5db3643..f45325c 100644
--- a/src/in_process_server.cpp
+++ b/src/in_process_server.cpp
@@ -1984,6 +1984,22 @@
     impl->add_pointer_button_notification(on_button);
 }
 
+uint32_t wlcs::Client::move_pointer_to(Pointer& pointer, int x, int y)
+{
+    std::optional<uint32_t> enter_serial;
+    add_pointer_enter_notification(
+        [&](auto, auto, auto)
+        {
+            // Inside the enter handler the client's latest serial is, by
+            // definition, the enter serial.
+            enter_serial = latest_serial();
+            return false;
+        });
+    pointer.move_to(x, y);
+    dispatch_until([&]{ return enter_serial.has_value(); });
+    return enter_serial.value();
+}
+
 void wlcs::Client::dispatch_until(std::function<bool()> const& predicate, std::chrono::seconds timeout)
 {
     impl->dispatch_until(predicate, timeout);
diff --git a/tests/ext_image_copy_capture_v1.cpp b/tests/ext_image_copy_capture_v1.cpp
index 1a93e13..35fdd77 100644
--- a/tests/ext_image_copy_capture_v1.cpp
+++ b/tests/ext_image_copy_capture_v1.cpp
@@ -538,15 +538,8 @@
     // Create a surface and place the cursor over it
     wlcs::Surface surface{client.create_visible_surface(200, 200)};
     the_server().move_surface_to(surface, 0, 0);
-    std::optional<uint32_t> enter_serial;
-    client.add_pointer_enter_notification([&](auto, auto, auto)
-        {
-            enter_serial = client.latest_serial();
-            return false;
-        });
     auto pointer = the_server().create_pointer();
-    pointer.move_to(100, 100);
-    client.dispatch_until([&]() { return enter_serial.has_value(); });
+    auto const enter_serial = client.move_pointer_to(pointer, 100, 100);
 
     // Set a cursor image
     wlcs::Surface cursor_surface{client};
@@ -555,7 +548,7 @@
     memset(data.data(), 0xff, data.size());
     wl_surface_attach(cursor_surface, cursor_buffer1, 0, 0);
     wl_surface_commit(cursor_surface);
-    wl_pointer_set_cursor(client.the_pointer(), enter_serial.value(), cursor_surface, 16, 16);
+    wl_pointer_set_cursor(client.the_pointer(), enter_serial, cursor_surface, 16, 16);
     client.roundtrip();
 
     // Create a cursor session for the output
diff --git a/tests/wl_pointer.cpp b/tests/wl_pointer.cpp
index f401d3e..02b509c 100644
--- a/tests/wl_pointer.cpp
+++ b/tests/wl_pointer.cpp
@@ -46,18 +46,8 @@
     /// notably set_cursor, must be issued with a valid enter serial.
     auto move_pointer_to_surface() -> uint32_t
     {
-        std::optional<uint32_t> enter_serial;
-        client.add_pointer_enter_notification(
-            [&](auto, auto, auto)
-            {
-                // Inside the enter handler the client's latest serial is, by
-                // definition, the enter serial.
-                enter_serial = client.latest_serial();
-                return false;
-            });
-        pointer.move_to(surface_x + pointer_offset_x, surface_y + pointer_offset_y);
-        client.dispatch_until([&]{ return enter_serial.has_value(); });
-        return enter_serial.value();
+        return client.move_pointer_to(
+            pointer, surface_x + pointer_offset_x, surface_y + pointer_offset_y);
     }
 
     /// A role-less surface suitable for use as a cursor.