- Rename Kotlin package from com.example.mycrib to com.example.casera - Update Android app name, namespace, and application ID - Update iOS bundle identifiers and project settings - Rename iOS directories (MyCribTests -> CaseraTests, etc.) - Update deep link schemes from mycrib:// to casera:// - Update app group identifiers - Update subscription product IDs - Update all UI strings and branding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
362 lines
15 KiB
Swift
362 lines
15 KiB
Swift
import XCTest
|
|
|
|
/// Task management tests
|
|
/// Uses UITestHelpers for consistent login/logout behavior
|
|
/// IMPORTANT: Tasks require at least one residence to exist
|
|
final class TaskTests: XCTestCase {
|
|
var app: XCUIApplication!
|
|
|
|
override func setUpWithError() throws {
|
|
continueAfterFailure = false
|
|
app = XCUIApplication()
|
|
app.launch()
|
|
|
|
// Ensure user is logged in
|
|
UITestHelpers.ensureLoggedIn(app: app)
|
|
|
|
// CRITICAL: Ensure at least one residence exists
|
|
// Tasks are disabled if no residences exist
|
|
ensureResidenceExists()
|
|
|
|
// Now navigate to Tasks tab
|
|
navigateToTasksTab()
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
app = nil
|
|
}
|
|
|
|
// MARK: - Helper Methods
|
|
|
|
/// Ensures at least one residence exists (required for tasks to work)
|
|
private func ensureResidenceExists() {
|
|
// Navigate to Residences tab
|
|
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
|
|
if residencesTab.waitForExistence(timeout: 5) {
|
|
residencesTab.tap()
|
|
sleep(2)
|
|
|
|
// Check if we have any residences
|
|
// Look for the add button - if we see "Add a property" text or empty state, create one
|
|
let emptyStateText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'No properties' OR label CONTAINS[c] 'No residences'")).firstMatch
|
|
|
|
if emptyStateText.exists {
|
|
// No residences exist, create a quick one
|
|
let addButton = app.buttons[AccessibilityIdentifiers.Residence.addButton]
|
|
if addButton.waitForExistence(timeout: 5) {
|
|
addButton.tap()
|
|
sleep(2)
|
|
|
|
// Fill minimal required fields
|
|
let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
|
|
if nameField.waitForExistence(timeout: 5) {
|
|
nameField.tap()
|
|
nameField.typeText("Test Home for Tasks")
|
|
|
|
// Scroll to address fields
|
|
app.swipeUp()
|
|
sleep(1)
|
|
|
|
// Fill required address fields
|
|
let streetField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Street'")).firstMatch
|
|
if streetField.exists {
|
|
streetField.tap()
|
|
streetField.typeText("123 Test St")
|
|
}
|
|
|
|
let cityField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'City'")).firstMatch
|
|
if cityField.exists {
|
|
cityField.tap()
|
|
cityField.typeText("TestCity")
|
|
}
|
|
|
|
let stateField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'State'")).firstMatch
|
|
if stateField.exists {
|
|
stateField.tap()
|
|
stateField.typeText("TS")
|
|
}
|
|
|
|
let postalField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Postal' OR placeholderValue CONTAINS[c] 'Zip'")).firstMatch
|
|
if postalField.exists {
|
|
postalField.tap()
|
|
postalField.typeText("12345")
|
|
}
|
|
|
|
// Save
|
|
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
|
|
if saveButton.exists {
|
|
saveButton.tap()
|
|
sleep(3) // Wait for save to complete
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func navigateToTasksTab() {
|
|
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
|
|
if tasksTab.waitForExistence(timeout: 5) {
|
|
if !tasksTab.isSelected {
|
|
tasksTab.tap()
|
|
sleep(3) // Give it time to load
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Finds the Add Task button using multiple strategies
|
|
/// The button exists in two places:
|
|
/// 1. Toolbar (always visible when residences exist)
|
|
/// 2. Empty state (visible when no tasks exist)
|
|
private func findAddTaskButton() -> XCUIElement {
|
|
sleep(2) // Wait for screen to fully render
|
|
|
|
// Strategy 1: Try accessibility identifier
|
|
let addButtonById = app.buttons[AccessibilityIdentifiers.Task.addButton].firstMatch
|
|
if addButtonById.exists && addButtonById.isEnabled {
|
|
return addButtonById
|
|
}
|
|
|
|
// Strategy 2: Look for toolbar add button (navigation bar plus button)
|
|
let navBarButtons = app.navigationBars.buttons
|
|
for i in 0..<navBarButtons.count {
|
|
let button = navBarButtons.element(boundBy: i)
|
|
if button.label == "plus" || button.label.contains("Add") {
|
|
if button.isEnabled {
|
|
return button
|
|
}
|
|
}
|
|
}
|
|
|
|
// Strategy 3: Try finding "Add Task" button in empty state by text
|
|
let emptyStateButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add Task'")).firstMatch
|
|
if emptyStateButton.exists && emptyStateButton.isEnabled {
|
|
return emptyStateButton
|
|
}
|
|
|
|
// Strategy 4: Look for any enabled button with a plus icon
|
|
let allButtons = app.buttons
|
|
for i in 0..<min(allButtons.count, 20) { // Check first 20 buttons
|
|
let button = allButtons.element(boundBy: i)
|
|
if button.isEnabled && (button.label.contains("plus") || button.label.contains("Add")) {
|
|
return button
|
|
}
|
|
}
|
|
|
|
// Return the identifier one as fallback (will fail assertion if doesn't exist)
|
|
return addButtonById
|
|
}
|
|
|
|
// MARK: - Tests
|
|
|
|
func testTasksTabExists() {
|
|
// Given: User is logged in
|
|
// When: User looks for Tasks tab
|
|
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
|
|
|
|
// Then: Tasks tab should exist
|
|
XCTAssertTrue(tasksTab.exists, "Tasks tab should exist in main tab bar")
|
|
XCTAssertTrue(tasksTab.isSelected, "Tasks tab should be selected after navigation")
|
|
}
|
|
|
|
func testViewTasksList() {
|
|
// Given: User is on Tasks tab
|
|
navigateToTasksTab()
|
|
sleep(3)
|
|
|
|
// Then: Tasks screen should be visible
|
|
// Verify we're on the right screen by checking for the navigation title
|
|
let tasksTitle = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'All Tasks' OR label CONTAINS[c] 'Tasks'")).firstMatch
|
|
XCTAssertTrue(tasksTitle.waitForExistence(timeout: 5), "Tasks screen title should be visible")
|
|
}
|
|
|
|
func testAddTaskButtonExists() {
|
|
// Given: User is on Tasks tab with at least one residence
|
|
navigateToTasksTab()
|
|
sleep(3)
|
|
|
|
// Then: Add task button should exist and be enabled
|
|
let addButton = findAddTaskButton()
|
|
XCTAssertTrue(addButton.exists, "Add task button should exist on Tasks screen")
|
|
XCTAssertTrue(addButton.isEnabled, "Add task button should be enabled when residence exists")
|
|
}
|
|
|
|
func testNavigateToAddTask() {
|
|
// Given: User is on Tasks tab
|
|
navigateToTasksTab()
|
|
sleep(3)
|
|
|
|
// When: User taps add task button
|
|
let addButton = findAddTaskButton()
|
|
XCTAssertTrue(addButton.exists, "Add task button should exist")
|
|
XCTAssertTrue(addButton.isEnabled, "Add task button should be enabled")
|
|
|
|
addButton.tap()
|
|
sleep(3)
|
|
|
|
// Then: Should show add task form with required fields
|
|
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title' OR placeholderValue CONTAINS[c] 'Task'")).firstMatch
|
|
XCTAssertTrue(titleField.waitForExistence(timeout: 5), "Task title field should appear in add form")
|
|
|
|
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
|
|
XCTAssertTrue(saveButton.exists, "Save button should exist in add task form")
|
|
}
|
|
|
|
func testCancelTaskCreation() {
|
|
// Given: User is on add task form
|
|
navigateToTasksTab()
|
|
sleep(3)
|
|
|
|
let addButton = findAddTaskButton()
|
|
XCTAssertTrue(addButton.exists && addButton.isEnabled, "Add task button should exist and be enabled")
|
|
addButton.tap()
|
|
sleep(3)
|
|
|
|
// Verify form opened
|
|
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch
|
|
XCTAssertTrue(titleField.waitForExistence(timeout: 5), "Task form should open")
|
|
|
|
// When: User taps cancel
|
|
let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch
|
|
XCTAssertTrue(cancelButton.exists, "Cancel button should exist in task form")
|
|
cancelButton.tap()
|
|
sleep(2)
|
|
|
|
// Then: Should return to tasks list
|
|
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
|
|
XCTAssertTrue(tasksTab.exists, "Should be back on tasks list after cancel")
|
|
}
|
|
|
|
func testCreateBasicTask() {
|
|
// Given: User is on Tasks tab
|
|
navigateToTasksTab()
|
|
sleep(3)
|
|
|
|
// When: User taps add task button
|
|
let addButton = findAddTaskButton()
|
|
XCTAssertTrue(addButton.exists && addButton.isEnabled, "Add task button should exist and be enabled")
|
|
addButton.tap()
|
|
sleep(3)
|
|
|
|
// Verify task form loaded
|
|
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch
|
|
XCTAssertTrue(titleField.waitForExistence(timeout: 5), "Task title field should appear")
|
|
|
|
// Fill in task title with unique timestamp
|
|
let timestamp = Int(Date().timeIntervalSince1970)
|
|
let taskTitle = "UITest Task \(timestamp)"
|
|
titleField.tap()
|
|
titleField.typeText(taskTitle)
|
|
|
|
// Scroll down to find and fill description
|
|
app.swipeUp()
|
|
sleep(1)
|
|
|
|
let descField = app.textViews.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Description'")).firstMatch
|
|
if descField.exists {
|
|
descField.tap()
|
|
descField.typeText("Test task")
|
|
}
|
|
|
|
// Scroll to find Save button
|
|
app.swipeUp()
|
|
sleep(1)
|
|
|
|
// When: User taps save
|
|
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
|
|
XCTAssertTrue(saveButton.exists, "Save button should exist")
|
|
saveButton.tap()
|
|
|
|
// Then: Should return to tasks list
|
|
sleep(5) // Wait for API call to complete
|
|
|
|
// Verify we're back on tasks list by checking tab exists
|
|
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
|
|
XCTAssertTrue(tasksTab.exists, "Should be back on tasks list after saving")
|
|
|
|
// Verify task appears in the list (may be in kanban columns)
|
|
let newTask = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(taskTitle)'")).firstMatch
|
|
XCTAssertTrue(newTask.waitForExistence(timeout: 10), "New task '\(taskTitle)' should appear in the list")
|
|
}
|
|
|
|
func testViewTaskDetails() {
|
|
// Given: User is on Tasks tab and at least one task exists
|
|
navigateToTasksTab()
|
|
sleep(3)
|
|
|
|
// Look for any task in the list
|
|
let taskCard = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'UITest Task' OR label CONTAINS 'Test'")).firstMatch
|
|
|
|
if !taskCard.waitForExistence(timeout: 5) {
|
|
// No task found - skip this test
|
|
print("⚠️ No tasks found - run testCreateBasicTask first")
|
|
return
|
|
}
|
|
|
|
// When: User taps on a task
|
|
taskCard.tap()
|
|
sleep(2)
|
|
|
|
// Then: Should show task details screen
|
|
let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch
|
|
let completeButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Complete' OR label CONTAINS[c] 'Mark'")).firstMatch
|
|
let backButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Back' OR label CONTAINS[c] 'Tasks'")).firstMatch
|
|
|
|
let detailScreenVisible = editButton.exists || completeButton.exists || backButton.exists
|
|
XCTAssertTrue(detailScreenVisible, "Task details screen should show with action buttons")
|
|
}
|
|
|
|
func testNavigateToContractors() {
|
|
// Given: User is on Tasks tab
|
|
navigateToTasksTab()
|
|
sleep(1)
|
|
|
|
// When: User taps Contractors tab
|
|
let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
|
|
XCTAssertTrue(contractorsTab.waitForExistence(timeout: 5), "Contractors tab should exist")
|
|
contractorsTab.tap()
|
|
sleep(1)
|
|
|
|
// Then: Should be on Contractors tab
|
|
XCTAssertTrue(contractorsTab.isSelected, "Contractors tab should be selected")
|
|
}
|
|
|
|
func testNavigateToDocuments() {
|
|
// Given: User is on Tasks tab
|
|
navigateToTasksTab()
|
|
sleep(1)
|
|
|
|
// When: User taps Documents tab
|
|
let documentsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Documents'")).firstMatch
|
|
XCTAssertTrue(documentsTab.waitForExistence(timeout: 5), "Documents tab should exist")
|
|
documentsTab.tap()
|
|
sleep(1)
|
|
|
|
// Then: Should be on Documents tab
|
|
XCTAssertTrue(documentsTab.isSelected, "Documents tab should be selected")
|
|
}
|
|
|
|
func testNavigateBetweenTabs() {
|
|
// Given: User is on Tasks tab
|
|
navigateToTasksTab()
|
|
sleep(1)
|
|
|
|
// When: User navigates to Residences tab
|
|
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
|
|
XCTAssertTrue(residencesTab.exists, "Residences tab should exist")
|
|
residencesTab.tap()
|
|
sleep(1)
|
|
|
|
// Then: Should be on Residences tab
|
|
XCTAssertTrue(residencesTab.isSelected, "Should be on Residences tab")
|
|
|
|
// When: User navigates back to Tasks
|
|
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
|
|
tasksTab.tap()
|
|
sleep(2)
|
|
|
|
// Then: Should be back on Tasks tab
|
|
XCTAssertTrue(tasksTab.isSelected, "Should be back on Tasks tab")
|
|
}
|
|
}
|