| name | save-verify-strategy |
| description | Post-form-save verification (Toast / redirect / data comparison / rich-text clear). Trigger: save verification, toast, redirect detection, Meta+A, save success detection. |
Form Save Verification Strategy
I. macOS Rich-Text Editor Select-All Must Use Meta+A
In macOS Chromium, Control+A is the Emacs shortcut (moves cursor to start of line), not "select all". In rich-text editors like CKEditor / TinyMCE / Quill, Control+A cannot select all content, leaving old content behind.
❌ Anti-pattern:
def fill_rich_text(self, text: str):
self.click(self.EDITOR)
self.page.keyboard.press("Control+A")
self.page.keyboard.press("Backspace")
self.page.keyboard.type(text)
✅ Best practice:
def fill_rich_text(self, text: str):
self.click(self.EDITOR)
self.page.keyboard.press("Meta+A")
self.page.keyboard.press("Backspace")
self.page.keyboard.type(text)
Diagnostic signal: Data entered has leftover old content at the end (e.g. "2026-05-06 10:30:00123" with extra 123 at the end) → select-all shortcut not working.
II. Post-Save Verification Strategy: Three-Level Selection
| Post-Save Behavior | Verification Method | Reliability | Applicable Scenario |
|---|
| Shows Toast | is_visible(toast_locator) — must detect immediately after networkidle | Low (disappears in 2-3s) | Scenarios with a clear Toast |
| Page redirect | Detect editing page signature element disappears wait_for(state="hidden") | Medium (persistent state) | Scenarios where save triggers auto-redirect |
| No UI feedback | Write marker data → reopen → read and compare | High (data-level verification) | No Toast, uncertain about redirect |
Principle: The three levels can be combined. Prioritize persistent state changes; Toast is supplementary only.
2.1 Toast Timing Trap
wait_for_timeout() in the click_save() method consumes Toast's lifetime. Must detect Toast immediately after networkidle — do not wait first and then detect.
❌ Anti-pattern:
def click_save(self):
self.click(self.SAVE_BTN)
self.page.wait_for_load_state("networkidle")
self.page.wait_for_timeout(3000)
assert page.is_visible(toast)
✅ Best practice:
def click_save(self):
self.click(self.SAVE_BTN)
self.page.wait_for_load_state("networkidle")
is_ok = page.is_visible(toast, timeout=10000)
page.wait_for_timeout(3000)
2.2 Redirect Detection
After saving, the page may redirect away from the editing page. Confirm save success by detecting that the editing page's signature element disappears:
def is_save_success(self) -> bool:
"""Editing page header disappears = save successful and left editing page"""
try:
self.page.locator(self.EDIT_PAGE_HEADER).first.wait_for(
state="hidden", timeout=15000
)
return True
except Exception:
return False
2.3 Redirect Destination Cannot Be Assumed
The redirect destination after saving may differ depending on navigation context (direct URL access vs. SPA in-app navigation vs. operation in a new tab). Do not assert a redirect to a specific page.
❌ Anti-pattern:
redirected_home = HomePage(page)
assert redirected_home.is_page_loaded()
✅ Best practice:
assert edit_page.is_save_success()
III. Multi-Tab Post-Save: Close Old Tab, Re-Enter from Original Tab
After saving, if you need to re-visit the same page to verify data, do not try to navigate within the redirected page (redirect destination is uncertain, elements may not exist). Close the old tab and re-open a new tab from the original tab (stable state).
❌ Anti-pattern:
redirected_page = SomePage(new_page)
assert redirected_page.is_page_loaded()
redirected_page.click(breadcrumb)
✅ Best practice:
finally:
new_page.close()
assert original_home.is_page_loaded()
new_page_2 = original_home.open_target_in_new_tab(name)
try:
finally:
new_page_2.close()
IV. Data Comparison Verification Pattern (Final Verification)
When there is no Toast or UI feedback, use "write marker → save → reopen → read and compare" as the final verification loop.
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
edit_page.fill_description(timestamp)
write_test_data("marker_key", timestamp)
saved = read_test_data().get("marker_key")
actual = edit_page.get_description_text()
allure.attach(saved, name="Expected timestamp", ...)
allure.attach(actual, name="Actual timestamp", ...)
assert actual == saved, (
f"Data mismatch: expected='{saved}', actual='{actual}'"
)
Key points:
- Marker data must be written to config file (shared across test cases, not in-memory variables)
- Regardless of assertion outcome, use
allure.attach() to write both expected and actual values to the report
- Assertion failure message must include comparison of both values for easy debugging