diff --git a/scripts/workflow-test.mjs b/scripts/workflow-test.mjs
index a4d9331..1747664 100644
--- a/scripts/workflow-test.mjs
+++ b/scripts/workflow-test.mjs
@@ -29,7 +29,7 @@ function requireEnv(name) {
}
// ---------------------------------------------------------------------------
-// Session signing (same as smoke test)
+// Session signing
// ---------------------------------------------------------------------------
function signSession(user) {
@@ -43,11 +43,6 @@ function signSession(user) {
return `${payload}.${signature}`;
}
-// ---------------------------------------------------------------------------
-// HTML helpers
-// ---------------------------------------------------------------------------
-
-/** Decode HTML entities that Next.js uses in attribute values */
function decodeHtml(str) {
return str
.replace(/"/g, '"')
@@ -74,37 +69,109 @@ async function getPage(path, cookie) {
}
// ---------------------------------------------------------------------------
-// Server action invocation
-//
-// Next.js 15 embeds server action references as hidden fields inside
-// forms that use useActionState / form action={...}. Each form contains:
-// $ACTION_REF_N - action reference marker
-// $ACTION_N:0 - JSON with {"id":"","bound":"$@1"}
-// $ACTION_N:1 - JSON-encoded state value
-// $ACTION_KEY - CSRF protection key
-//
-// To invoke a server action from an external script:
-// 1. GET the page that hosts the form (with a valid session cookie)
-// 2. Locate the correct ", formStart);
if (formEnd === -1) return null;
const rawForm = html.substring(formStart, formEnd);
+ const fields = extractHiddenFields(rawForm);
+ for (const key of fields.keys()) {
+ if (key.startsWith("$ACTION_REF_") || key.startsWith("$ACTION_ID_")) return fields;
+ }
+ return null;
+}
- const fields = new Map();
+/**
+ * Extract all hidden input fields from a form that contains a field with the
+ * given name. Returns a Map of field name \u2192 decoded value, or null if not found.
+ */
+function extractActionRefsByField(html, fieldName) {
+ const fieldPos = html.indexOf(`name="${fieldName}"`);
+ if (fieldPos === -1) return null;
+ const searchStart = html.lastIndexOf("