1fbda5badde574c10884e5339fdda1f5b7495990
braney
  Thu Sep 24 13:45:30 2026 -0700
docent: regression script for the Sessions page Replace keeping who can load it, refs #38311

rm38311 saves a private session from the save card, saves over it with the
private box unticked, and checks that the row still has its lock.  It passes on
hgwbeta (v504) and genome-test, and fails on hgw0 (v503).

Docent changes it needed:
- a fill: verb, to type into any form field
- login: no longer dies when a navigation is still under way during its
bad-password check, which is what broke it on ticket parks
- a park is driven on its https port, because hgLogin posts its form to
https:// on the port it was reached on; the parks' self-signed certificate
is accepted on loopback targets only
- hgw0 maps to hgcentral, and a park's https port finds its hg.conf

diff --git src/hg/utils/docent/tests/regress/rm38311.docent.yaml src/hg/utils/docent/tests/regress/rm38311.docent.yaml
new file mode 100644
index 00000000000..fb663314d43
--- /dev/null
+++ src/hg/utils/docent/tests/regress/rm38311.docent.yaml
@@ -0,0 +1,68 @@
+# #38311 -- on the new Sessions page, Replace on the save card reset who could load the
+# session.  Saving under a name you already use asks "Replace this session?", and the Replace
+# button re-posted the save (hgS_doSaveSessionJson), which always writes shared=1
+# (saveCartAsSession(..., 1) in hgSession.c).  A private session became shared by link, and
+# a session in the Public Sessions gallery dropped out of it.
+#
+# 0cdd15681f1, hg/js/hgSession.js.  Replace now posts hgS_doOverwriteJson, which reads the
+# row's own shared value and keeps it.  The dialog gained "Who can load it stays as it is."
+#
+# The script saves a PRIVATE session, then saves over the same name from the save card with
+# the private box left unticked, and asserts the row still carries its lock.  The description
+# typed on the second save is how the script knows the Replace went through: the row only
+# gains its (i) icon after the save chain has finished and the page has reloaded, so waiting
+# for that icon waits for the answer, and a Replace that did nothing cannot pass.
+#
+# The page is opted into with sessionNewPage=1 on the URL, so no hg.conf gate is needed.
+# It saves under the docentTest account of whichever central the target reads, so it needs
+# a [section] for that central in ~/.docentLogin (hgcentraltest, hgcentralbeta, hgcentral).
+# The session is deleted at the start, in case an earlier run died before its own cleanup,
+# and again at the end.  The two deletes are plain GETs of the JSON endpoint, which answers
+# {"error": "Could not find that session."} when there is nothing to delete.
+#
+# v503 FAILS AT THE DIALOG, NOT AT THE LOCK.  v503's save card has no Replace dialog at all:
+# it saves over an existing name straight away (fc8de100a34 added the dialog, in v504), and
+# that save writes shared=1 just the same.  So on a v503 server the step that waits for
+# #sessCfOk is the one that goes red.
+#
+# A failed run skips the cleanup and leaves rm38311 behind; the next run's first step
+# deletes it.  On a ticket park, run against the HTTPS port: hgLogin writes its form action
+# as https:// on the port it was reached on, so on the http port the password never arrives.
+proof:
+  - "release-ab 2026-09-24 -- passes on hgwbeta (v504) and genome-test, fails on hgw0 (v503) at step 12, the wait for the Replace dialog, which v503 does not have; the row it left on hgcentral had gone from shared=0 to shared=1, the bug as filed"
+  - "sandbox-ab 2026-09-24 -- ts park 38311 (master) with only hg/js/hgSession.js swapped for a copy with 0cdd15681f1 reverted: fails at step 13, the dialog sentence; with that check taken out it fails at step 16, the lock, and the row reads shared=1. The real hgSession.js back in the same park passes"
+
+target: genome-test
+db: hg38
+size: [1400, 900]
+reset: true
+fast: true
+steps:
+  - login: true
+  - goto: "/cgi-bin/hgSession?hgS_doDeleteJson=1&hgS_oldSessionName=rm38311"
+
+  # Save rm38311 as a private session.
+  - goto: "/cgi-bin/hgSession?sessionNewPage=1"
+  - wait: '#sessSaveName'
+  - expect:
+      noHas: '#sessionAppTable tr:has(a:text-is("rm38311"))'
+  - fill: {'#sessSaveName': rm38311}
+  - click: '#sessSavePrivate'
+  - click: '#sessSaveBtn'
+  - wait: '#sessionAppTable tr:has(a:text-is("rm38311")) .sessLockWrap'
+
+  # Save over it from the save card, private box NOT ticked, with a description.
+  - fill: {'#sessSaveName': rm38311, '#sessSaveDesc': "rm38311 replaced"}
+  - click: '#sessSaveBtn'
+  - wait: '#sessCfOk'
+  - expect:
+      text: ["Replace this session?", "Who can load it stays as it is."]
+  - click: '#sessCfOk'
+  - wait: '#sessionAppTable tr:has(a:text-is("rm38311")) .sessInfo'
+  - expect:
+      has: '#sessionAppTable tr:has(a:text-is("rm38311")) .sessLockWrap'
+
+  # Clean up.
+  - goto: "/cgi-bin/hgSession?hgS_doDeleteJson=1&hgS_oldSessionName=rm38311"
+  - expect:
+      text: '"success": true'