@@ -13,6 +13,9 @@ final class EntryEditUITests: KeeForgeUITestCase {
1313 if name. contains ( " testSaveConflictOffersReloadAndConflictCopy " ) {
1414 app. launchEnvironment [ " UI_TEST_LOCAL_SAVE_CONFLICT_COUNT " ] = " 2 "
1515 }
16+ if name. contains ( " testLockWhileDirtyPromptsConfirmationThenLocks " ) {
17+ app. launchEnvironment [ " UI_TEST_LOCAL_SAVE_CONFLICT_COUNT " ] = " 1 "
18+ }
1619 }
1720
1821 func testCreateEntrySavesAndShowsInList( ) {
@@ -23,7 +26,6 @@ final class EntryEditUITests: KeeForgeUITestCase {
2326 username: " ui-created-user " ,
2427 password: " created-password-123 "
2528 )
26- persistDatabaseChanges ( )
2729 lockAndReopenVault ( )
2830
2931 openEntry ( named: createdEntryTitle)
@@ -44,9 +46,7 @@ final class EntryEditUITests: KeeForgeUITestCase {
4446 XCTAssertTrue ( titleField. waitForExistence ( timeout: 5 ) )
4547 replaceText ( in: titleField, with: editedDiscordTitle)
4648 app. buttons [ " entry-edit.save " ] . tap ( )
47-
48- XCTAssertTrue ( waitForUnsavedIndicator ( isPresent: true ) )
49- persistDatabaseChanges ( )
49+ XCTAssertTrue ( waitForElementToDisappear ( app. buttons [ " entry-edit.save " ] , timeout: 10 ) )
5050 tapBackButton ( )
5151 tapBackButton ( )
5252 lockAndReopenVault ( )
@@ -68,12 +68,10 @@ final class EntryEditUITests: KeeForgeUITestCase {
6868 XCTAssertTrue ( deleteButton. waitForExistence ( timeout: 5 ) )
6969 deleteButton. tap ( )
7070 app. alerts. buttons [ " Delete " ] . tap ( )
71-
72- XCTAssertTrue ( waitForUnsavedIndicator ( isPresent: true ) )
73- persistDatabaseChanges ( )
71+ waitForAutosaveAttempt ( )
7472
7573 XCTAssertFalse ( entry ( named: " Twitter " ) . exists)
76- tapBackButton ( )
74+ lockAndReopenVault ( )
7775
7876 openGroup ( named: " Recycle Bin " )
7977 XCTAssertTrue ( revealElement ( entry ( named: " Twitter " ) ) , " Twitter entry was not moved into the recycle bin " )
@@ -127,8 +125,7 @@ final class EntryEditUITests: KeeForgeUITestCase {
127125 useButton. tap ( )
128126
129127 app. buttons [ " entry-edit.save " ] . tap ( )
130- XCTAssertTrue ( waitForUnsavedIndicator ( isPresent: true ) )
131- persistDatabaseChanges ( )
128+ XCTAssertTrue ( waitForElementToDisappear ( app. buttons [ " entry-edit.save " ] , timeout: 10 ) )
132129 lockAndReopenVault ( )
133130
134131 openEntry ( named: generatedPasswordEntryTitle)
@@ -160,7 +157,7 @@ final class EntryEditUITests: KeeForgeUITestCase {
160157 openGroup ( named: " Social " )
161158 openEntry ( named: " Discord " )
162159 editCurrentEntryTitle ( to: firstConflictDiscordTitle)
163- triggerDatabaseSaveConflict ( )
160+ XCTAssertTrue ( waitForSaveConflictAlert ( ) )
164161
165162 let reloadButton = app. buttons [ " save-conflict.reload " ]
166163 let saveAsCopyButton = app. buttons [ " save-conflict.save-as-copy " ]
@@ -176,7 +173,7 @@ final class EntryEditUITests: KeeForgeUITestCase {
176173 openGroup ( named: " Social " )
177174 openEntry ( named: " Discord " )
178175 editCurrentEntryTitle ( to: secondConflictDiscordTitle)
179- triggerDatabaseSaveConflict ( )
176+ XCTAssertTrue ( waitForSaveConflictAlert ( ) )
180177
181178 XCTAssertTrue ( saveAsCopyButton. waitForExistence ( timeout: 5 ) )
182179 saveAsCopyButton. tap ( )
@@ -253,6 +250,7 @@ final class EntryEditUITests: KeeForgeUITestCase {
253250 let saveButton = app. buttons [ " entry-edit.save " ]
254251 XCTAssertTrue ( saveButton. waitForExistence ( timeout: 5 ) , " Entry editor save button was not visible " , file: file, line: line)
255252 saveButton. tap ( )
253+ XCTAssertTrue ( waitForElementToDisappear ( saveButton, timeout: 10 ) , " Entry editor did not dismiss after autosave " , file: file, line: line)
256254 }
257255
258256 private func editCurrentEntryTitle( to title: String , file: StaticString = #filePath, line: UInt = #line) {
@@ -267,24 +265,7 @@ final class EntryEditUITests: KeeForgeUITestCase {
267265 let saveButton = app. buttons [ " entry-edit.save " ]
268266 XCTAssertTrue ( saveButton. waitForExistence ( timeout: 5 ) , " Entry editor save button was not visible " , file: file, line: line)
269267 saveButton. tap ( )
270- XCTAssertTrue ( waitForUnsavedIndicator ( isPresent: true ) , " Unsaved indicator did not appear after editing " , file: file, line: line)
271- }
272-
273- private func triggerDatabaseSaveConflict( file: StaticString = #filePath, line: UInt = #line) {
274- let saveButton = app. buttons [ " database.save " ]
275- XCTAssertTrue ( saveButton. waitForExistence ( timeout: 5 ) , " Database save button was not visible " , file: file, line: line)
276- saveButton. tap ( )
277-
278- let alert = app. alerts [ " Save Conflict " ]
279- XCTAssertTrue ( alert. waitForExistence ( timeout: 10 ) , " Save conflict alert did not appear " , file: file, line: line)
280- }
281-
282- private func persistDatabaseChanges( file: StaticString = #filePath, line: UInt = #line) {
283- let saveButton = app. buttons [ " database.save " ]
284- XCTAssertTrue ( saveButton. waitForExistence ( timeout: 5 ) , " Database save button was not visible " , file: file, line: line)
285- XCTAssertTrue ( saveButton. isEnabled, " Database save button was disabled unexpectedly " , file: file, line: line)
286- saveButton. tap ( )
287- XCTAssertFalse ( waitForUnsavedIndicator ( isPresent: true , timeout: 10 ) , " Unsaved indicator did not clear after saving " , file: file, line: line)
268+ XCTAssertTrue ( waitForElementToDisappear ( saveButton, timeout: 10 ) , " Entry editor did not dismiss after autosave " , file: file, line: line)
288269 }
289270
290271 private func lockAndReopenVault( file: StaticString = #filePath, line: UInt = #line) {
@@ -332,12 +313,35 @@ final class EntryEditUITests: KeeForgeUITestCase {
332313 return indicator. exists == isPresent
333314 }
334315
316+ private func waitForSaveConflictAlert( timeout: TimeInterval = 10 ) -> Bool {
317+ app. alerts [ " Save Conflict " ] . waitForExistence ( timeout: timeout)
318+ }
319+
320+ private func waitForElementToDisappear(
321+ _ element: XCUIElement ,
322+ timeout: TimeInterval = 5
323+ ) -> Bool {
324+ let deadline = Date ( ) . addingTimeInterval ( timeout)
325+
326+ repeat {
327+ if element. exists == false {
328+ return true
329+ }
330+ RunLoop . current. run ( until: Date ( ) . addingTimeInterval ( 0.2 ) )
331+ } while Date ( ) < deadline
332+
333+ return element. exists == false
334+ }
335+
336+ private func waitForAutosaveAttempt( timeout: TimeInterval = 2 ) {
337+ RunLoop . current. run ( until: Date ( ) . addingTimeInterval ( timeout) )
338+ }
339+
335340 private func tapBackButton( file: StaticString = #filePath, line: UInt = #line) {
336341 let navigationBar = app. navigationBars. firstMatch
337342 XCTAssertTrue ( navigationBar. waitForExistence ( timeout: 5 ) , " Navigation bar was not visible " , file: file, line: line)
338343
339344 let excludedIdentifiers = Set ( [
340- " database.save " ,
341345 " entry-list.add-entry " ,
342346 " lock.button " ,
343347 " sort.menu " ,
0 commit comments